/*
 * Copyright 2014 Actelion Pharmaceuticals Ltd., Gewerbestrasse 16, CH-4123 Allschwil, Switzerland
 *
 * This file is part of DataWarrior.
 * 
 * DataWarrior is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 * 
 * DataWarrior is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along with DataWarrior.
 * If not, see http://www.gnu.org/licenses/.
 *
 * @author Thomas Sander
 */

package com.actelion.research.gui;

import com.actelion.research.chem.*;
import com.actelion.research.chem.reaction.Reaction;
import com.actelion.research.chem.reaction.ReactionArrow;
import com.actelion.research.chem.reaction.ReactionMapper;
import com.actelion.research.gui.clipboard.IClipboardHandler;
import com.actelion.research.gui.dnd.MoleculeDropAdapter;
import com.actelion.research.util.CursorHelper;

import javax.swing.*;
import javax.swing.text.html.HTMLEditorKit;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

public class JDrawArea extends JPanel
    implements ActionListener, KeyListener, MouseListener, MouseMotionListener
{
    static final long serialVersionUID = 0x20061019;

    public static final int MODE_MULTIPLE_FRAGMENTS = 1;
    public static final int MODE_MARKUSH_STRUCTURE = 2;
    public static final int MODE_REACTION = 4;
    public static final int MODE_DRAWING_OBJECTS = 8;

    private static final int MAX_CONNATOMS = 8;
    private static final int MIN_BOND_LENGTH_SQUARE = 100;

    private static final int KEY_IS_ATOM_LABEL = 1;
    private static final int KEY_IS_SUBSTITUENT = 2;
    private static final int KEY_IS_VALID_START = 3;
    private static final int KEY_IS_INVALID = 4;

    private static final float FRAGMENT_MAX_CLICK_DISTANCE = 24.0f;
    private static final float FRAGMENT_GROUPING_DISTANCE = 1.4f;    // in average bond lengths
    private static final float FRAGMENT_CLEANUP_DISTANCE = 1.5f;    // in average bond lengths
    private static final float DEFAULT_ARROW_LENGTH = 0.08f;        // relative to panel width

    private static final boolean USE_GRAPHICS_2D = true;

    public static final Color SELECTION_COLOR = new Color(161, 204, 255);
    public static final Color CHAIN_HILITE_COLOR = new Color(204, 222, 255);

    protected static final int UPDATE_NONE = 0;
    protected static final int UPDATE_REDRAW = 1;
    // redraw molecules and drawing objects with their current coordinates
    protected static final int UPDATE_CHECK_VIEW = 2;
    // redraw with on-the-fly coordinate transformation only if current coords do not fit within view area
    // (new coords are generated for one paint() only; the original coords are not changed)
    protected static final int UPDATE_CHECK_COORDS = 3;
    // redraw with in-place coordinate transformation only if current coords do not fit within view area
    // (the original atom and object coords are replaced by the new ones)
    protected static final int UPDATE_SCALE_COORDS = 4;
    // redraw with in-place coordinate transformation; molecules and objects are scaled to fill
    // the view unless the maximum average bond length reaches the optimum.
    protected static final int UPDATE_SCALE_COORDS_USE_FRAGMENTS = 5;
    // as UPDATE_SCALE_COORDS but uses fragments from mFragment rather than creating a
    // fresh mFragment list from mMol. Used for setting a reaction or fragment list from outside.
    protected static final int UPDATE_INVENT_COORDS = 6;
    // redraw with in-place coordinate transformation; first all molecules' coordinates
    // are generated from scratch, then molecules and objects are scaled to fill
    // the view unless the maximum average bond length reaches the optimum.

    private static final int ALLOWED_DROP_ACTIONS = DnDConstants.ACTION_COPY_OR_MOVE;

    private static final int cRequestNone = 0;
    private static final int cRequestNewBond = 1;
    private static final int cRequestNewChain = 2;
    private static final int cRequestMoveSingle = 3;
    private static final int cRequestMoveSelected = 4;
    private static final int cRequestLassoSelect = 5;
    private static final int cRequestSelectRect = 6;
    private static final int cRequestZoomAndRotate = 7;
    private static final int cRequestMapAtoms = 8;
    private static final int cRequestCopySelected = 9;
    private static final int cRequestMoveObject = 10;
    private static final int cRequestCopyObject = 11;
    public static final int FAKE_ATOM_NO = 100;

    private Dimension mSize;
    private int mMode, mChainAtoms, mCurrentTool, mOtherAtom, mOtherMass, mOtherValence, mOtherRadical,
        mCurrentHiliteAtom, mCurrentHiliteBond, mPendingRequest,
        mCurrentCursor, mReactantCount, mUpdateMode, mDisplayMode, mAtom1, mAtom2;
    private int[] mChainAtom, mFragmentNo, mHiliteBondSet;
    private float mX1, mY1, mX2, mY2;
    private float[] mX, mY, mChainAtomX, mChainAtomY;
    private boolean mShiftIsDown, mControlIsDown, mMouseIsDown, mIsAddingToSelection, mAtomColorSupported;
    private boolean[] mIsSelectedAtom, mIsSelectedObject;
    private String[] mAtomText;
    private ExtendedDepictor mDepictor;
    private StereoMolecule mMol;        // molecule being modified directly by the drawing editor
    private Molecule mUndoMol;   // molecule in undo buffer
    private StereoMolecule[] mFragment;    // in case of MODE_MULTIPLE_FRAGMENTS contains valid stereo fragments
    // for internal and external read-only-access (reconstructed at any change)
    private DrawingObjectList mDrawingObjectList, mUndoDrawingObjectList;
    private AbstractDrawingObject mCurrentHiliteObject;
    private Polygon mLassoRegion;
    private ArrayList<DrawAreaListener> mListeners;
    private IClipboardHandler mClipboardHandler;
    private JDialog mHelpDialog;
    private StringBuilder mAtomKeyStrokeBuffer;

    /**
     * @param mol  an empty or valid stereo molecule
     * @param mode 0 or a meaningful combination of the mode flags, e.g. MODE_REACTION | MODE_DRAWING_OBJECTS
     */
    public JDrawArea(StereoMolecule mol, int mode)
    {
        mMol = mol;
        mMode = mode;

        setFocusable(true);
        addKeyListener(this);
        addMouseListener(this);
        addMouseMotionListener(this);

        mListeners = new ArrayList<DrawAreaListener>();

        mCurrentTool = JDrawToolbar.cToolStdBond;
        mCurrentHiliteAtom = -1;
        mCurrentHiliteBond = -1;
        mCurrentHiliteObject = null;
        mAtom1 = -1;
        mOtherAtom = 6;
        mOtherMass = 0;
        mOtherValence = -1;
        mOtherRadical = 0;
        mPendingRequest = cRequestNone;
        mCurrentCursor = CursorHelper.cPointerCursor;
        mAtomKeyStrokeBuffer = new StringBuilder();

        if ((mMode & (MODE_REACTION | MODE_MARKUSH_STRUCTURE)) != 0) {
            mMode |= (MODE_MULTIPLE_FRAGMENTS);
        }

        if ((mMode & (MODE_DRAWING_OBJECTS | MODE_REACTION)) != 0) {
            mDrawingObjectList = new DrawingObjectList();
        }

        mUpdateMode = UPDATE_SCALE_COORDS;

        initializeDragAndDrop(ALLOWED_DROP_ACTIONS);

/*		dumpBytesOfGif("/chainCursor.gif");
        dumpBytesOfGif("/deleteCursor.gif");
		dumpBytesOfGif("/handCursor.gif");
		dumpBytesOfGif("/handPlusCursor.gif");
		dumpBytesOfGif("/fistCursor.gif");
		dumpBytesOfGif("/lassoCursor.gif");
		dumpBytesOfGif("/lassoPlusCursor.gif");
		dumpBytesOfGif("/rectCursor.gif");
		dumpBytesOfGif("/rectPlusCursor.gif");	*/
    }

    /**
     * Call this after initialization to get clipboard support
     *
     * @param h
     */
    public void setClipboardHandler(IClipboardHandler h)
    {
        mClipboardHandler = h;
    }

    private void update(int mode)
    {
        mUpdateMode = Math.max(mUpdateMode, mode);
        repaint();
    }

    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

        Dimension theSize = getSize();
        if (mSize == null || mSize.width != theSize.width || mSize.height != theSize.height) {
            mSize = theSize;
            if (mUpdateMode < UPDATE_CHECK_COORDS) {
                mUpdateMode = UPDATE_CHECK_COORDS;
            }
        }

        if ((mMode & MODE_REACTION) != 0 && mDrawingObjectList.size() == 0) {
            float mx = 0.5f * (float) theSize.width;
            float my = 0.5f * (float) theSize.height;
            float dx = 0.5f * DEFAULT_ARROW_LENGTH * (float) theSize.width;
            ReactionArrow arrow = new ReactionArrow();
            arrow.setCoordinates(mx - dx, my, mx + dx, my);
            arrow.setDeletable(false);
            mDrawingObjectList.add(arrow);
        }

        boolean isScaledView = false;
        if (mUpdateMode != UPDATE_NONE) {
            if ((mMode & MODE_MULTIPLE_FRAGMENTS) != 0
                && mUpdateMode != UPDATE_SCALE_COORDS_USE_FRAGMENTS) {
                analyzeFragmentMembership();
            }

            mDepictor = ((mMode & MODE_REACTION) != 0) ?
                new ExtendedDepictor(new Reaction(mFragment, mReactantCount), mDrawingObjectList, false, USE_GRAPHICS_2D)
                : ((mMode & MODE_MARKUSH_STRUCTURE) != 0) ?
                new ExtendedDepictor(mFragment, mReactantCount, mDrawingObjectList, USE_GRAPHICS_2D)
                : ((mMode & MODE_MULTIPLE_FRAGMENTS) != 0) ?
                new ExtendedDepictor(mFragment, mDrawingObjectList, USE_GRAPHICS_2D)
                : new ExtendedDepictor(mMol, mDrawingObjectList, USE_GRAPHICS_2D);

            mDepictor.setFragmentNoColor(((mMode & MODE_MULTIPLE_FRAGMENTS) != 0) ?
                getBackground().brighter() : null);
            mDepictor.setDisplayMode(AbstractDepictor.cDModeHiliteAllQueryFeatures
                | ((mCurrentTool == JDrawToolbar.cToolMapper) ?
                AbstractDepictor.cDModeShowMapping
                    | AbstractDepictor.cDModeSuppressCIPParity
                    | mDisplayMode : mDisplayMode));

            if ((mMode & (MODE_REACTION | MODE_MARKUSH_STRUCTURE | MODE_MULTIPLE_FRAGMENTS)) == 0) {
                mDepictor.getMoleculeDepictor(0).setAtomText(mAtomText);
            }

            switch (mUpdateMode) {
            case UPDATE_INVENT_COORDS:
            case UPDATE_SCALE_COORDS:
            case UPDATE_SCALE_COORDS_USE_FRAGMENTS:
                cleanupCoordinates(g, mDepictor);
                break;
            case UPDATE_CHECK_COORDS:
                DepictorTransformation t1 = mDepictor.updateCoords(g, new Rectangle2D.Float(0, 0, theSize.width, theSize.height), 0);
                if (t1 != null && (mMode & MODE_MULTIPLE_FRAGMENTS) != 0) {
                    // in fragment mode depictor transforms mFragment[] rather than mMol
                    t1.applyTo(mMol);
                }
                break;
            case UPDATE_CHECK_VIEW:
                DepictorTransformation t2 = mDepictor.validateView(g, new Rectangle2D.Float(0, 0, theSize.width, theSize.height), 0);
                isScaledView = (t2 != null && !t2.isVoidTransformation());
                break;
            }

            mUpdateMode = UPDATE_NONE;
        }

        if (mDepictor != null) {
            mDepictor.paintFragmentNumbers(g);
        }

        // don't hilite anything when the view is scaled and object coords don't reflect screen coords
        if (!isScaledView) {
            drawHiliting(g);
        }

        if (mDepictor != null) {
            mDepictor.paintStructures(g);
            mDepictor.paintDrawingObjects(g);
        }

        if (mCurrentHiliteAtom != -1 && mAtomKeyStrokeBuffer.length() != 0) {
            int x = (int)mMol.getAtomX(mCurrentHiliteAtom);
            int y = (int)mMol.getAtomY(mCurrentHiliteAtom);
            String s = mAtomKeyStrokeBuffer.toString();
            int validity = getAtomKeyStrokeValidity(s);
            g.setColor((validity == KEY_IS_ATOM_LABEL) ? Color.BLACK
            		 : (validity == KEY_IS_SUBSTITUENT) ? Color.BLUE
            		 : (validity == KEY_IS_VALID_START) ? Color.GRAY : Color.RED);
            if (validity == KEY_IS_INVALID)
            	s = s + "<unknown>";
            g.setFont(new Font("Helvetica",0, 24));
        	g.drawString(s, x, y);
        }

        g.setColor(Color.black);
        switch (mPendingRequest) {
        case cRequestNewBond:
            int x1, y1, x2, y2, xdiff, ydiff;
            x1 = (int) mX1;
            y1 = (int) mY1;
            if (mCurrentHiliteAtom == -1 || mCurrentHiliteAtom == mAtom1) {
                x2 = (int) mX2;
                y2 = (int) mY2;
            } else {
                x2 = (int) mMol.getAtomX(mCurrentHiliteAtom);
                y2 = (int) mMol.getAtomY(mCurrentHiliteAtom);
            }
            switch (mCurrentTool) {
            case JDrawToolbar.cToolStdBond:
                g.drawLine(x1, y1, x2, y2);
                break;
            case JDrawToolbar.cToolUpBond:
                int[] x = new int[3];
                int[] y = new int[3];
                xdiff = (y1 - y2) / 9;
                ydiff = (x2 - x1) / 9;
                x[0] = x1;
                y[0] = y1;
                x[1] = x2 + xdiff;
                y[1] = y2 + ydiff;
                x[2] = x2 - xdiff;
                y[2] = y2 - ydiff;
                g.fillPolygon(x, y, 3);
                break;
            case JDrawToolbar.cToolDownBond:
                int xx1, xx2, yy1, yy2;
                xdiff = x2 - x1;
                ydiff = y2 - y1;
                for (int i = 2; i < 17; i += 2) {
                    xx1 = x1 + i * xdiff / 17 - i * ydiff / 128;
                    yy1 = y1 + i * ydiff / 17 + i * xdiff / 128;
                    xx2 = x1 + i * xdiff / 17 + i * ydiff / 128;
                    yy2 = y1 + i * ydiff / 17 - i * xdiff / 128;
                    g.drawLine(xx1, yy1, xx2, yy2);
                }
                break;
            }
            break;
        case cRequestNewChain:
            if (mChainAtoms > 0) {
                g.drawLine((int) mX1, (int) mY1, (int) mChainAtomX[0], (int) mChainAtomY[0]);
            }
            if (mChainAtoms > 1) {
                for (int i = 1; i < mChainAtoms; i++) {
                    g.drawLine((int) mChainAtomX[i - 1], (int) mChainAtomY[i - 1],
                        (int) mChainAtomX[i], (int) mChainAtomY[i]);
                }
            }
            break;
        case cRequestLassoSelect:
            g.setColor(Color.gray);
            g.drawPolygon(mLassoRegion);
            g.setColor(Color.black);
            break;
        case cRequestSelectRect:
            int x = (mX1 < mX2) ? (int) mX1 : (int) mX2;
            int y = (mY1 < mY2) ? (int) mY1 : (int) mY2;
            int w = (int) Math.abs(mX2 - mX1);
            int h = (int) Math.abs(mY2 - mY1);
            g.setColor(Color.gray);
            g.drawRect(x, y, w, h);
            g.setColor(Color.black);
            break;
        case cRequestMapAtoms:
            x1 = (int) mX1;
            y1 = (int) mY1;
            if (mCurrentHiliteAtom == -1 || mCurrentHiliteAtom == mAtom1) {
                x2 = (int) mX2;
                y2 = (int) mY2;
            } else {
                x2 = (int) mMol.getAtomX(mCurrentHiliteAtom);
                y2 = (int) mMol.getAtomY(mCurrentHiliteAtom);
            }
            g.setColor(new Color(192, 0, 0));
            g.drawLine(x1, y1, x2, y2);
            g.setColor(Color.black);
            break;
        }
    }

    private void drawHiliting(Graphics g)
    {
        if (mHiliteBondSet != null) {
            g.setColor(CHAIN_HILITE_COLOR);
            for (int i = 0; i < mHiliteBondSet.length; i++) {
                hiliteBond(g, mHiliteBondSet[i]);
            }
        }

        if (mCurrentHiliteAtom != -1) {
            g.setColor(SELECTION_COLOR);
            hiliteAtom(g, mCurrentHiliteAtom);
            if (mCurrentTool == JDrawToolbar.cToolMapper) {
                int mapNo = mMol.getAtomMapNo(mCurrentHiliteAtom);
                if (mapNo != 0) {
                    for (int atom = 0; atom < mMol.getAtoms(); atom++) {
                        if (atom != mCurrentHiliteAtom
                            && mMol.getAtomMapNo(atom) == mapNo) {
                            hiliteAtom(g, atom);
                        }
                    }
                }
            }
        }

        if (mCurrentHiliteBond != -1) {
            g.setColor(SELECTION_COLOR);
            hiliteBond(g, mCurrentHiliteBond);
        }

        if (mCurrentHiliteObject != null) {
            mCurrentHiliteObject.hilite(g);
        }
    }

    private void hiliteAtom(Graphics g, int atom)
    {
        int x = (int) mMol.getAtomX(atom);
        int y = (int) mMol.getAtomY(atom);
        g.fillOval(x - 9, y - 9, 18, 18);
    }

    private void hiliteBond(Graphics g, int bond)
    {
        int x1 = (int) mMol.getAtomX(mMol.getBondAtom(0, bond));
        int y1 = (int) mMol.getAtomY(mMol.getBondAtom(0, bond));
        int x2 = (int) mMol.getAtomX(mMol.getBondAtom(1, bond));
        int y2 = (int) mMol.getAtomY(mMol.getBondAtom(1, bond));

        Stroke oldStroke = ((Graphics2D) g).getStroke();
        ((Graphics2D) g).setStroke(new BasicStroke((float) 12, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        ((Graphics2D) g).drawLine(x1, y1, x2, y2);
        ((Graphics2D) g).setStroke(oldStroke);
    }

    public void addDrawAreaListener(DrawAreaListener l)
    {
        mListeners.add(l);
    }

    protected void buttonPressed(int button)
    {
        switch (button) {
            case JDrawToolbar.cButtonClear:
                if (mDrawingObjectList != null) {
                    mDrawingObjectList.clear();
                    update(UPDATE_REDRAW);
                }
                storeState();
                boolean isFragment = mMol.isFragment();
                mMol.deleteMolecule();
                mMol.setFragment(isFragment);
                if (mUndoMol.getAllAtoms() != 0) {
                    fireMoleculeChanged();
                }
                update(UPDATE_REDRAW);
                return;
            case JDrawToolbar.cButtonCleanStructure:
                storeState();
                fireMoleculeChanged();
                update(UPDATE_INVENT_COORDS);
                return;
            case JDrawToolbar.cButtonUndo:
                restoreState();
                fireMoleculeChanged();
                update(UPDATE_CHECK_VIEW);
                return;
        }
    }

    public void toolChanged(int newTool)
    {
        if (mCurrentTool != newTool) {
            setOtherAtom(-1, 0, -1, 0);
            if (mCurrentTool == JDrawToolbar.cToolMapper
                || newTool == JDrawToolbar.cToolMapper) {
                update(UPDATE_REDRAW);
            }

            mCurrentTool = newTool;
        }
    }

    private void setOtherAtom(int atomicNo, int mass, int valence, int radical)
    {
        mOtherAtom = atomicNo;
        mOtherMass = mass;
        mOtherValence = valence;
        mOtherRadical = radical;
    }

    public void actionPerformed(ActionEvent e)
    {
        String command = e.getActionCommand();
        if (command.equals("Copy")) {
            copy();
        } else if (command.equals("Paste")) {
            paste();
        } else if (command.startsWith("atomColor")) {
            int index = command.indexOf(':');
            int atom = Integer.parseInt(command.substring(9, index));
            int color = Integer.parseInt(command.substring(index + 1));
            if (mMol.isSelectedAtom(atom)) {
                for (int i = 0; i < mMol.getAtoms(); i++) {
                    if (mMol.isSelectedAtom(i)) {
                        mMol.setAtomColor(i, color);
                    }
                }
            } else {
                mMol.setAtomColor(atom, color);
            }
        }
    }

    private void copy()
    {
        boolean isReaction = ((mMode & MODE_REACTION) != 0);
        boolean selectionFound = false;
        boolean isBothSideSelection = false;
        boolean isOnProductSide = false;
        ReactionArrow arrow = null;

        for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
            if (mMol.isSelectedAtom(atom)) {
                if (!selectionFound) {
                    selectionFound = true;
                    if (!isReaction) {
                        break;
                    }

                    arrow = (ReactionArrow) mDrawingObjectList.get(0);
                    isOnProductSide = arrow.isOnProductSide(mMol.getAtomX(atom), mMol.getAtomY(atom));
                } else {
                    if (isOnProductSide != arrow.isOnProductSide(mMol.getAtomX(atom), mMol.getAtomY(atom))) {
                        isBothSideSelection = true;
                        break;
                    }
                }
            }
        }

        if (isReaction) {
            if (isBothSideSelection) {
                copyReaction(true);
            } else if (selectionFound) {
                copyMolecule(true);
            } else {
                copyReaction(false);
            }
        } else {
            copyMolecule(selectionFound);
        }
    }

    private boolean copyReaction(boolean selectionOnly)
    {
        Reaction rx = selectionOnly ? getSelectedReaction() : getReaction();
        if (rx != null && mClipboardHandler != null) {
            return mClipboardHandler.copyReaction(rx);
        }

        return false;
    }

    private Reaction getSelectedReaction()
    {
        Reaction rxn = new Reaction();
        for (int i = 0; i < mFragment.length; i++) {
            StereoMolecule selectedMol = getSelectedCopy(mFragment[i]);
            if (selectedMol != null) {
                if (i < mReactantCount) {
                    rxn.addReactant(selectedMol);
                } else {
                    rxn.addProduct(selectedMol);
                }
            }
        }
        return rxn;
    }

    private StereoMolecule getSelectedCopy(StereoMolecule sourceMol)
    {
        int atomCount = 0;
        for (int atom = 0; atom < sourceMol.getAllAtoms(); atom++) {
            if (sourceMol.isSelectedAtom(atom)) {
                atomCount++;
            }
        }

        if (atomCount == 0) {
            return null;
        }

        int bondCount = 0;
        for (int bond = 0; bond < sourceMol.getAllBonds(); bond++) {
            if (sourceMol.isSelectedBond(bond)) {
                bondCount++;
            }
        }

        boolean[] includeAtom = new boolean[sourceMol.getAllAtoms()];
        for (int atom = 0; atom < sourceMol.getAllAtoms(); atom++) {
            includeAtom[atom] = sourceMol.isSelectedAtom(atom);
        }

        StereoMolecule destMol = new StereoMolecule(atomCount, bondCount);
        sourceMol.copyMoleculeByAtoms(destMol, includeAtom, false, null);
        return destMol;
    }

    private boolean copyMolecule(boolean selectionOnly)
    {
        if (mMol.getAllAtoms() != 0 && mClipboardHandler != null) {
            return mClipboardHandler.copyMolecule(selectionOnly ? getSelectedCopy(mMol) : mMol);
        }

        return false;
    }

    private void paste()
    {
        if ((mMode & MODE_REACTION) != 0) {
            if (pasteReaction()) {
                return;
            }
        }

        pasteMolecule();
    }

    private boolean pasteReaction()
    {
        boolean ret = false;
        if (mClipboardHandler != null) {
            Reaction rxn = mClipboardHandler.pasteReaction();
            if (rxn != null) {
                for (int i = 0; i < rxn.getMolecules(); i++) {
                    rxn.getMolecule(i).setFragment(mMol.isFragment());
                }
                setReaction(rxn);
                ret = true;
            }
        }
        return ret;
    }

    private boolean pasteMolecule()
    {
        boolean ret = false;
        if (mClipboardHandler != null) {
            StereoMolecule mol = mClipboardHandler.pasteMolecule();
            if (mol != null && mol.getAllAtoms() != 0) {
                if (mMol.getAllAtoms() == 0) {
                    boolean isFragment = mMol.isFragment();
                    mol.copyMolecule(mMol);
                    mMol.setFragment(isFragment);
                    moleculeChanged(true);
                } else {
                    int avbl = (int) mMol.getAverageBondLength();
                    Depictor d = new Depictor(mol);
                    d.updateCoords(this.getGraphics(), new Rectangle2D.Float(0, 0,
                            this.getWidth(),
                            this.getHeight()),
                        AbstractDepictor.cModeInflateToMaxAVBL + avbl
                    );
                    int originalAtoms = mMol.getAllAtoms();
                    mMol.addMolecule(mol);
                    for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
                        mMol.setAtomSelection(atom, atom >= originalAtoms);
                    }
                    moleculeChanged(true);
                }
            }
            ret = true;
        }
        return ret;
    }
    // end changes CxR

    public void mousePressed(MouseEvent e)
    {
        mAtomKeyStrokeBuffer.setLength(0);

        if (handlePopupTrigger(e)) {
            return;
        }

        if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {
            if (e.getClickCount() == 2) {
                return;
            }

            mMouseIsDown = true;
            updateCursor();
            mousePressedButton1(e);
        }
    }

    public void mouseReleased(MouseEvent e)
    {
        if (handlePopupTrigger(e)) {
            return;
        }

        if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) {
            if (e.getClickCount() == 2) {
                handleDoubleClick(e);
                return;
            }

            mMouseIsDown = false;
            updateCursor();
            mouseReleasedButton1(e);
        }
    }

    public void mouseEntered(MouseEvent e)
    {
        requestFocus();
        updateCursor();
    }

    public void mouseExited(MouseEvent e)
    {
    }

    public void mouseClicked(MouseEvent e)
    {
    }

    public void mouseDragged(MouseEvent e)
    {
        mMouseIsDown = true;
        mX2 = e.getX();
        mY2 = e.getY();

        boolean repaintNeeded = trackHiliting(mX2, mY2, true);

        switch (mPendingRequest) {
            case cRequestNewChain:
                float lastX, lastY;
                if (mChainAtoms > 0) {
                    lastX = mChainAtomX[mChainAtoms - 1];
                    lastY = mChainAtomY[mChainAtoms - 1];
                } else {
                    lastX = 0;
                    lastY = 0;
                }
                float avbl = mMol.getAverageBondLength();
                float s0 = (int) avbl;
                float s1 = (int) (0.866 * avbl);
                float s2 = (int) (0.5 * avbl);
                float dx = mX2 - mX1;
                float dy = mY2 - mY1;
                if (Math.abs(dy) > Math.abs(dx)) {
                    mChainAtoms = (int) (2 * Math.abs(dy) / (s0 + s2));
                    if (Math.abs(dy) % (s0 + s2) > s0) {
                        mChainAtoms++;
                    }
                    mChainAtomX = new float[mChainAtoms];
                    mChainAtomY = new float[mChainAtoms];
                    if (mX2 < mX1) {
                        s1 = -s1;
                    }
                    if (mY2 < mY1) {
                        s0 = -s0;
                        s2 = -s2;
                    }
                    for (int i = 0; i < mChainAtoms; i++) {
                        mChainAtomX[i] = mX1 + ((i + 1) / 2) * s1;
                        mChainAtomY[i] = mY1 + ((i + 1) / 2) * (s0 + s2);
                        if ((i & 1) == 0) {
                            mChainAtomY[i] += s0;
                        }
                    }
                } else {
                    mChainAtoms = (int) (Math.abs(dx) / s1);
                    mChainAtomX = new float[mChainAtoms];
                    mChainAtomY = new float[mChainAtoms];
                    if (mX2 < mX1) {
                        s1 = -s1;
                    }
                    if (mY2 < mY1) {
                        s2 = -s2;
                    }
                    for (int i = 0; i < mChainAtoms; i++) {
                        mChainAtomX[i] = mX1 + (i + 1) * s1;
                        mChainAtomY[i] = mY1;
                        if ((i & 1) == 0) {
                            mChainAtomY[i] += s2;
                        }
                    }
                }
                if (mChainAtoms > 0) {
                    mChainAtom = new int[mChainAtoms];
                    for (int i = 0; i < mChainAtoms; i++) {
                        mChainAtom[i] = mMol.findAtom(mChainAtomX[i], mChainAtomY[i]);
                        if (mChainAtom[i] != -1) {
                            mChainAtomX[i] = mMol.getAtomX(mChainAtom[i]);
                            mChainAtomY[i] = mMol.getAtomY(mChainAtom[i]);
                        }
                    }
                    if (mChainAtomX[mChainAtoms - 1] != lastX
                        || mChainAtomY[mChainAtoms - 1] != lastY) {
                        repaintNeeded = true;
                    }
                } else if (lastX != 0 || lastY != 0) {
                    repaintNeeded = true;
                }
                break;
            case cRequestNewBond:
                if ((mX2 - mX1) * (mX2 - mX1) + (mY2 - mY1) * (mY2 - mY1) < MIN_BOND_LENGTH_SQUARE) {
                    suggestNewX2AndY2(mAtom1);
                }

                repaintNeeded = true;
                break;
            case cRequestMoveSingle:
                mMol.setAtomX(mAtom1, mX[mAtom1] + mX2 - mX1);
                mMol.setAtomY(mAtom1, mY[mAtom1] + mY2 - mY1);
                if (mAtom2 != -1) {
                    mMol.setAtomX(mAtom2, mX[mAtom2] + mX2 - mX1);
                    mMol.setAtomY(mAtom2, mY[mAtom2] + mY2 - mY1);
                }

                update(UPDATE_CHECK_VIEW);
                break;
            case cRequestCopySelected:
                duplicateSelected();
                mPendingRequest = cRequestMoveSelected;
            case cRequestMoveSelected:
                if (mDrawingObjectList != null) {
                    for (AbstractDrawingObject drawingObject : mDrawingObjectList) {
                        if (drawingObject.isSelected()) {
                            drawingObject.translate(mX2, mY2);
                        }
                    }
                }
                for (int i = 0; i < mMol.getAllAtoms(); i++) {
                    if (mMol.isSelectedAtom(i)) {
                        mMol.setAtomX(i, mX[i] + mX2 - mX1);
                        mMol.setAtomY(i, mY[i] + mY2 - mY1);
                    }
                }
                update(UPDATE_CHECK_VIEW);
                break;
            case cRequestCopyObject:
                mDrawingObjectList.add(mCurrentHiliteObject.clone());
                mPendingRequest = cRequestMoveObject;
            case cRequestMoveObject:
                mCurrentHiliteObject.translate(mX2, mY2);
                update(UPDATE_CHECK_VIEW);
                break;
            case cRequestZoomAndRotate:
                boolean selectedAtomsFound = false;
                for (int atom = 0; atom < mMol.getAllAtoms() && !selectedAtomsFound; atom++) {
                    selectedAtomsFound = mMol.isSelectedAtom(atom);
                }
                boolean selectedObjectsFound = false;
                if (mDrawingObjectList != null) {
                    for (int i = 0; i < mDrawingObjectList.size() && !selectedObjectsFound; i++) {
                        selectedObjectsFound = ((AbstractDrawingObject) mDrawingObjectList.get(i)).isSelected();
                    }
                }
                float magnification = (Math.abs(mY2 - mY1) < 20) ? 1.0f : (float) Math.exp((mY2 - mY1) / 100f);
                float angleChange = (Math.abs(mX2 - mX1) < 20) ? 0.0f : (mX2 - mX1) / 50;
                boolean selectedOnly = (selectedAtomsFound || selectedObjectsFound);
                if (mDrawingObjectList != null && (!selectedOnly || selectedObjectsFound)) {
                    for (int i = 0; i < mDrawingObjectList.size(); i++) {
                        if (!selectedOnly || ((AbstractDrawingObject) mDrawingObjectList.get(i)).isSelected()) {
                            ((AbstractDrawingObject) mDrawingObjectList.get(i)).zoomAndRotate(magnification, angleChange);
                        }
                    }
                    update(UPDATE_CHECK_VIEW);
                }
                if (!selectedOnly || selectedAtomsFound) {
                    mMol.zoomAndRotate(magnification, angleChange, selectedOnly);
                    update(UPDATE_CHECK_VIEW);
                }
                break;
            case cRequestLassoSelect:
                if ((Math.abs(mX2 - mLassoRegion.xpoints[mLassoRegion.npoints - 1]) < 3)
                    && (Math.abs(mY2 - mLassoRegion.ypoints[mLassoRegion.npoints - 1]) < 3)) {
                    break;
                }

                mLassoRegion.npoints--;
                mLassoRegion.addPoint((int) mX2, (int) mY2);
                mLassoRegion.addPoint((int) mX1, (int) mY1);
            case cRequestSelectRect:
                Shape selectedRegion = null;
                if (mPendingRequest == cRequestLassoSelect) {
                    selectedRegion = mLassoRegion;
                } else {
                    selectedRegion = new Rectangle((int) Math.min(mX1, mX2), (int) Math.min(mY1, mY2),
                        (int) Math.abs(mX2 - mX1), (int) Math.abs(mY2 - mY1));
                }
                if (mDrawingObjectList != null) {
                    for (int i = 0; i < mDrawingObjectList.size(); i++) {
                        AbstractDrawingObject object = (AbstractDrawingObject) mDrawingObjectList.get(i);
                        boolean isSelected = object.isSurroundedBy(selectedRegion);
                        if ((!mIsAddingToSelection || !mIsSelectedObject[i])
                            && (isSelected != object.isSelected())) {
                            object.setSelected(isSelected);
                            mUpdateMode = Math.max(mUpdateMode, UPDATE_REDRAW);
                        }
                    }
                }
                for (int i = 0; i < mMol.getAllAtoms(); i++) {
                    boolean isSelected = selectedRegion.contains((int) mMol.getAtomX(i), (int) mMol.getAtomY(i));
                    if ((!mIsAddingToSelection || !mIsSelectedAtom[i])
                        && (isSelected != mMol.isSelectedAtom(i))) {
                        mMol.setAtomSelection(i, isSelected);
                        mUpdateMode = Math.max(mUpdateMode, UPDATE_REDRAW);
                    }
                }
                repaintNeeded = true;
                break;
            case cRequestMapAtoms:
                repaintNeeded = true;
                break;
        }

        if (repaintNeeded) {
            repaint();
        }
    }

    public void mouseMoved(MouseEvent e)
    {
        mMouseIsDown = false;
        int x = e.getX();
        int y = e.getY();

        if (trackHiliting(x, y, false)) {
            repaint();
        }

        updateCursor();
    }

    public void keyPressed(KeyEvent e)
    {
        if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
            mShiftIsDown = true;
            updateCursor();
        }
        if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
            mControlIsDown = true;
            updateCursor();
        }

        if (mControlIsDown && e.getKeyCode() == KeyEvent.VK_Z) {
            restoreState();
            fireMoleculeChanged();
            update(UPDATE_CHECK_VIEW);
        }
        else if (e.getKeyCode() == KeyEvent.VK_DELETE) {
            storeState();
            if (mCurrentTool == JDrawToolbar.cToolMapper) {
                boolean found = false;
                for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
                    if (mMol.getAtomMapNo(atom) != 0) {
                        mMol.setAtomMapNo(atom, 0, false);
                        found = true;
                    }
                }
                if (found) {
                    fireMoleculeChanged();
                    update(UPDATE_REDRAW);
                }
            } else if (!deleteHilited()) {
                if (mMol.deleteSelectedAtoms()) {
                    fireMoleculeChanged();
                    update(UPDATE_REDRAW);
                }
            }
        } else if (e.getKeyCode() == KeyEvent.VK_HELP || (mCurrentHiliteAtom == -1 && e.getKeyChar() == '?')) {
            showHelpDialog();
            return;
        } else if (mCurrentHiliteBond != -1) {
            char ch = e.getKeyChar();
            if (ch == 'q' && mMol.isFragment()) {
                showBondQFDialog(mCurrentHiliteBond);
            } else if (ch >= '4' && ch <= '7') {
                if (mMol.addRingToBond(mCurrentHiliteBond, ch - '0', false)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
            } else if (ch == 'b') {
                if (mMol.addRingToBond(mCurrentHiliteBond, 6, true)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
            } else {
                boolean bondChanged = (ch == '1') ? changeHighlightedBond(Molecule.cBondTypeSingle)
                    : (ch == '2') ? changeHighlightedBond(Molecule.cBondTypeDouble)
                    : (ch == '3') ? changeHighlightedBond(Molecule.cBondTypeTriple)
                    : (ch == 'u') ? changeHighlightedBond(Molecule.cBondTypeUp)
                    : (ch == 'd') ? changeHighlightedBond(Molecule.cBondTypeDown)
                    : (ch == 'c') ? changeHighlightedBond(Molecule.cBondTypeCross)
                    : (ch == 'm') ? changeHighlightedBond(Molecule.cBondTypeMetalLigand)
                    : false;
                if (bondChanged) {
                    fireMoleculeChanged();
                    update(UPDATE_REDRAW);
                }
            }
        } else if (mCurrentHiliteAtom != -1) {
            char ch = e.getKeyChar();
            boolean isFirst = (mAtomKeyStrokeBuffer.length() == 0);
            if (isFirst && (ch == '+' || ch == '-')) {
                storeState();
                if (mMol.changeAtomCharge(mCurrentHiliteAtom, ch == '+')) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
            } else if (isFirst && ch == '.') {
                storeState();
                int newRadical = (mMol.getAtomRadical(mCurrentHiliteAtom) == Molecule.cAtomRadicalStateD) ?
                    0 : Molecule.cAtomRadicalStateD;
                mMol.setAtomRadical(mCurrentHiliteAtom, newRadical);
                fireMoleculeChanged();
                update(UPDATE_CHECK_COORDS);
            } else if (isFirst && ch == ':') {
                storeState();
                int newRadical = (mMol.getAtomRadical(mCurrentHiliteAtom) == Molecule.cAtomRadicalStateT) ? Molecule.cAtomRadicalStateS
                    : (mMol.getAtomRadical(mCurrentHiliteAtom) == Molecule.cAtomRadicalStateS) ? 0 : Molecule.cAtomRadicalStateT;
                mMol.setAtomRadical(mCurrentHiliteAtom, newRadical);
                fireMoleculeChanged();
                update(UPDATE_CHECK_COORDS);
            } else if (isFirst && ch == 'q' && mMol.isFragment()) {
                showAtomQFDialog(mCurrentHiliteAtom);
            } else if (isFirst && ch == '?') {
                storeState();
                if (mMol.changeAtom(mCurrentHiliteAtom, 0, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
            } else if (isFirst && ch > 48 && ch <= 57) {
                if (mMol.getFreeValence(mCurrentHiliteAtom) > 0) {
                    storeState();
                    int chainAtoms = ch - 47;
                    int atom1 = mCurrentHiliteAtom;
                    int hydrogenCount = mMol.getAllAtoms() - mMol.getAtoms();
                    for (int i = 1; i < chainAtoms; i++) {
                        suggestNewX2AndY2(atom1);
                        int atom2 = mMol.addAtom(mX2, mY2);
                        if (atom2 == -1) {
                            break;
                        }

                        mMol.addBond(atom1, atom2, Molecule.cBondTypeSingle);
                        atom1 = atom2 - hydrogenCount;    // new atom was added behind all hydrogens and travels now to the front
                        mMol.ensureHelperArrays(Molecule.cHelperNeighbours);
                    }
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
            } else if (!isFirst && e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            	mAtomKeyStrokeBuffer.setLength(0);
                update(UPDATE_REDRAW);
            } else if (!isFirst && e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
            	mAtomKeyStrokeBuffer.setLength(mAtomKeyStrokeBuffer.length()-1);
                update(UPDATE_REDRAW);
            } else if ((ch >= 65 && ch <= 90)
            		|| (ch >= 97 && ch <= 122)
            		|| (ch >= 48 && ch <= 57)
            		|| (ch == '-')) {
            	mAtomKeyStrokeBuffer.append(ch);
                update(UPDATE_REDRAW);
            } else if (ch == '\n' || ch == '\r') {
        		expandAtomKeyStrokes(mAtomKeyStrokeBuffer.toString());
            }
        }
    }

    private boolean changeHighlightedBond(int type)
    {
        storeState();
        return mMol.changeBond(mCurrentHiliteBond, type);
    }

    public void showHelpDialog()
    {
        if (mHelpDialog == null || !mHelpDialog.isVisible()) {
            JEditorPane helpPane = new JEditorPane();
            helpPane.setEditorKit(new HTMLEditorKit());
            helpPane.setEditable(false);
            try {
                helpPane.setPage(getClass().getResource("/html/editor/editor.html"));
            } catch (Exception ex) {
                helpPane.setText(ex.toString());
            }
            Component c = this;
            while (!(c instanceof Frame || c instanceof Dialog)) {
                c = c.getParent();
            }

            if (c instanceof Frame) {
                mHelpDialog = new JDialog((Frame) c, "Actelion Structure Editor Help", false);
            } else {
                mHelpDialog = new JDialog((Dialog) c, "Actelion Structure Editor Help", false);
            }

            mHelpDialog.setSize(520, 440);
            mHelpDialog.getContentPane().add(new JScrollPane(helpPane,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER));
            int x = (c.getX() >= 8 + mHelpDialog.getWidth()) ? c.getX() - 8 - mHelpDialog.getWidth() : c.getX() + 8 + c.getWidth();
            mHelpDialog.setLocation(x, c.getY());
            mHelpDialog.setVisible(true);
        } else {
            Component c = this;
            while (!(c instanceof Frame || c instanceof Dialog)) {
                c = c.getParent();
            }

            int x = (mHelpDialog.getX() + mHelpDialog.getWidth() / 2 >= c.getX() + c.getWidth() / 2) ?
                c.getX() - 8 - mHelpDialog.getWidth() : c.getX() + 8 + c.getWidth();
            mHelpDialog.setLocation(x, c.getY());
        }
    }

    public void keyReleased(KeyEvent e)
    {
        if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
            mShiftIsDown = false;
            updateCursor();
        }
        if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
            mControlIsDown = false;
            updateCursor();
        }
        if ((e.getModifiers() & Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) != 0) {
            if (e.getKeyCode() == KeyEvent.VK_C) {
                copy();
            } else if (e.getKeyCode() == KeyEvent.VK_V) {
                paste();
            }
        }
    }

    public void keyTyped(KeyEvent e)
    {
    }

    private boolean handlePopupTrigger(MouseEvent e)
    {
        if (e.isPopupTrigger()) {
            JPopupMenu popup = null;
            if (mClipboardHandler != null) {
                JMenuItem menuItem1 = new JMenuItem("Copy");
                menuItem1.addActionListener(this);
                if (mMol.getAllAtoms() == 0) {
                    menuItem1.setEnabled(false);
                }
                JMenuItem menuItem2 = new JMenuItem("Paste");
                menuItem2.addActionListener(this);
                if (popup == null) {
                    popup = new JPopupMenu();
                }
                popup.add(menuItem1);
                popup.add(menuItem2);
            }
            if (mAtomColorSupported && mCurrentHiliteAtom != -1) {
                int atomColor = mMol.getAtomColor(mCurrentHiliteAtom);
                if (popup == null) {
                    popup = new JPopupMenu();
                } else {
                    popup.addSeparator();
                }
                JMenu colorMenu = new JMenu("Set Atom Color");
                addColorToMenu(colorMenu, Color.BLACK, Molecule.cAtomColorBlack, atomColor == Molecule.cAtomColorBlack);
                addColorToMenu(colorMenu, Color.BLUE, Molecule.cAtomColorBlue, atomColor == Molecule.cAtomColorBlue);
                addColorToMenu(colorMenu, new Color(160, 0, 0), Molecule.cAtomColorDarkRed, atomColor == Molecule.cAtomColorDarkRed);
                addColorToMenu(colorMenu, Color.RED, Molecule.cAtomColorRed, atomColor == Molecule.cAtomColorRed);
                addColorToMenu(colorMenu, new Color(0, 160, 0), Molecule.cAtomColorDarkGreen, atomColor == Molecule.cAtomColorDarkGreen);
                addColorToMenu(colorMenu, Color.GREEN, Molecule.cAtomColorGreen, atomColor == Molecule.cAtomColorGreen);
                addColorToMenu(colorMenu, Color.MAGENTA, Molecule.cAtomColorMagenta, atomColor == Molecule.cAtomColorMagenta);
                addColorToMenu(colorMenu, Color.ORANGE, Molecule.cAtomColorOrange, atomColor == Molecule.cAtomColorOrange);
                popup.add(colorMenu);
            }
            if (popup != null) {
                popup.show(this, e.getX(), e.getY());
            }
            return true;
        }
        return false;
    }

    private void addColorToMenu(JMenu menu, Color color, int atomColor, boolean isSelected)
    {
        JRadioButtonMenuItem item = new JRadioButtonMenuItem("      ", isSelected);
        item.setActionCommand("atomColor" + mCurrentHiliteAtom + ":" + atomColor);
        item.addActionListener(this);
        item.setBackground(color);
        menu.add(item);
    }

    private void handleDoubleClick(MouseEvent e)
    {
        int x = e.getX();
        int y = e.getY();

        int atom = mMol.findAtom(x, y);
        int bond = mMol.findBond(x, y);

        if (mCurrentTool == JDrawToolbar.cToolLassoPointer) {
            if (mMol.isFragment()) {
                if (atom != -1) {
                    showAtomQFDialog(atom);
                } else if (bond != -1) {
                    showBondQFDialog(bond);
                } else if (mCurrentHiliteObject != null) {
                    if (!mShiftIsDown) {
                        for (int i = 0; i < mMol.getAllAtoms(); i++) {
                            mMol.setAtomSelection(i, false);
                        }
                        for (int i = 0; i < mDrawingObjectList.size(); i++) {
                            ((AbstractDrawingObject) mDrawingObjectList.get(i)).setSelected(false);
                        }
                    }

                    mCurrentHiliteObject.setSelected(true);
                    update(UPDATE_REDRAW);
                }
            } else {
                int rootAtom = -1;
                if (atom != -1) {
                    rootAtom = atom;
                } else if (bond != -1) {
                    rootAtom = mMol.getBondAtom(0, bond);
                }

                if (rootAtom != -1 || mCurrentHiliteObject != null) {
                    if (!mShiftIsDown) {
                        for (int i = 0; i < mMol.getAllAtoms(); i++) {
                            mMol.setAtomSelection(i, false);
                        }
                        if (mDrawingObjectList != null) {
                            for (AbstractDrawingObject drawingObject : mDrawingObjectList) {
                                drawingObject.setSelected(false);
                            }
                        }
                    }

                    if (rootAtom != -1) {
                        if ((mMode & MODE_MULTIPLE_FRAGMENTS) != 0) {
                            int fragment = mFragmentNo[rootAtom];
                            for (int i = 0; i < mMol.getAllAtoms(); i++) {
                                if (mFragmentNo[i] == fragment) {
                                    mMol.setAtomSelection(i, true);
                                }
                            }
                        } else {
                            int[] fragmentMember = mMol.getFragmentAtoms(rootAtom);
                            for (int i = 0; i < fragmentMember.length; i++) {
                                mMol.setAtomSelection(fragmentMember[i], true);
                            }
                        }
                    } else {
                        mCurrentHiliteObject.setSelected(true);
                    }

                    update(UPDATE_REDRAW);
                }
            }
        } else if (mCurrentTool == JDrawToolbar.cToolAtomOther) {
            if (atom != -1) {
                Component c = this;
                while (c.getParent() != null) {
                    c = c.getParent();
                }
                storeState();
                new JAtomLabelDialog((Frame) c, mMol, atom);
                mOtherAtom = mMol.getAtomicNo(atom);
                mOtherMass = mMol.getAtomMass(atom);
                mOtherValence = mMol.getAtomAbnormalValence(atom);
                mOtherRadical = mMol.getAtomRadical(atom);
                fireMoleculeChanged();
                update(UPDATE_REDRAW);
            }
        } else if (mCurrentTool == JDrawToolbar.cToolZoom) {
            int fragment = -2;
            if ((mMode & MODE_MULTIPLE_FRAGMENTS) != 0) {
                fragment = findFragment(x, y);
            }

            if (fragment != -1) {
                float minX = Integer.MAX_VALUE;
                float maxX = Integer.MIN_VALUE;
                for (int i = 0; i < mMol.getAllAtoms(); i++) {
                    if (fragment == -2 || mFragmentNo[i] == fragment) {
                        if (minX > mMol.getAtomX(i)) {
                            minX = mMol.getAtomX(i);
                        }
                        if (maxX < mMol.getAtomX(i)) {
                            maxX = mMol.getAtomX(i);
                        }
                    }
                }

                if (maxX > minX) {
                    float centerX = (maxX + minX) / 2;
                    for (int i = 0; i < mMol.getAllAtoms(); i++) {
                        if (fragment == -2 || mFragmentNo[i] == fragment) {
                            mMol.setAtomX(i, 2 * centerX - mMol.getAtomX(i));
                        }
                    }
                    for (int i = 0; i < mMol.getAllBonds(); i++) {
                        if (fragment == -2 || mFragmentNo[mMol.getBondAtom(0, i)] == fragment) {
                            switch (mMol.getBondType(i)) {
                                case Molecule.cBondTypeUp:
                                    mMol.setBondType(i, Molecule.cBondTypeDown);
                                    break;
                                case Molecule.cBondTypeDown:
                                    mMol.setBondType(i, Molecule.cBondTypeUp);
                                    break;
                            }
                        }
                    }
                }

                fireMoleculeChanged();
                update(UPDATE_REDRAW);
            }
        }
    }

    private void showAtomQFDialog(int atom)
    {
        Component c = this;
        while (c.getParent() != null) {
            c = c.getParent();
        }
        storeState();
        new JAtomQueryFeatureDialog((Frame) c, mMol, atom);
        fireMoleculeChanged();
        update(UPDATE_REDRAW);
    }

    private void showBondQFDialog(int bond)
    {
        Component c = this;
        while (c.getParent() != null) {
            c = c.getParent();
        }
        storeState();
        new JBondQueryFeatureDialog((Frame) c, mMol, bond);
        fireMoleculeChanged();
        update(UPDATE_REDRAW);
    }

    private void mousePressedButton1(MouseEvent e)
    {
        mX1 = e.getX();
        mY1 = e.getY();

        switch (mCurrentTool) {
            case JDrawToolbar.cToolZoom:
                // mX1,mY1 define anker for rotation and zooming
                float x = mX1;
                float y = mY1;

                mAtom1 = mMol.findAtom(mX1, mY1);
                if (mAtom1 != -1) {
                    mX1 = mMol.getAtomX(mAtom1);
                    mY1 = mMol.getAtomY(mAtom1);
                }

                mMol.zoomAndRotateInit(x, y);
                if (mDrawingObjectList != null) {
                    for (AbstractDrawingObject drawingObject : mDrawingObjectList) {
                        drawingObject.zoomAndRotateInit(x, y);
                    }
                }
                storeState();
                mPendingRequest = cRequestZoomAndRotate;
                break;
            case JDrawToolbar.cToolLassoPointer:
                mPendingRequest = cRequestNone;

                // if atom was hit -> move atom (and if atom is selected then all selected stuff)
                mAtom1 = mMol.findAtom(mX1, mY1);
                if (mAtom1 != -1) {
                    mAtom2 = -1;
                    mX1 = mMol.getAtomX(mAtom1);
                    mY1 = mMol.getAtomY(mAtom1);
                    if (mMol.isSelectedAtom(mAtom1)) {
                        mPendingRequest = mShiftIsDown ? cRequestCopySelected : cRequestMoveSelected;
                    } else {
                        mPendingRequest = cRequestMoveSingle;
                    }
                }

                // if bond was hit -> move bond (and if atom is selected then all selected stuff)
                if (mPendingRequest == cRequestNone) {
                    int bondClicked = mMol.findBond(mX1, mY1);
                    if (bondClicked != -1) {
                        mAtom1 = mMol.getBondAtom(0, bondClicked);
                        mAtom2 = mMol.getBondAtom(1, bondClicked);
                        if (mMol.isSelectedBond(bondClicked)) {
                            mPendingRequest = mShiftIsDown ? cRequestCopySelected : cRequestMoveSelected;
                        } else {
                            mPendingRequest = cRequestMoveSingle;
                        }
                    }
                }

                // if object was hit -> move object (and if atom is selected then all selected stuff)
                if (mPendingRequest == cRequestNone) {
                    if (mCurrentHiliteObject != null) {
                        if (mCurrentHiliteObject.isSelected()) {
                            mPendingRequest = mShiftIsDown ? cRequestCopySelected : cRequestMoveSelected;
                        } else {
                            mPendingRequest = (mShiftIsDown && !(mCurrentHiliteObject instanceof ReactionArrow)) ?
                                cRequestCopyObject : cRequestMoveObject;
                        }
                    }
                }

                if (mPendingRequest != cRequestNone) {
                    mX = new float[mMol.getAllAtoms()];
                    mY = new float[mMol.getAllAtoms()];
                    for (int i = 0; i < mMol.getAllAtoms(); i++) {
                        mX[i] = mMol.getAtomX(i);
                        mY[i] = mMol.getAtomY(i);
                    }
                    if (mDrawingObjectList != null) {
                        for (AbstractDrawingObject drawingObject : mDrawingObjectList) {
                            drawingObject.translateInit(mX1, mY1);
                        }
                    }

                    storeState();
                    break;
                }

                // is lasso- or rectangle-selecting
                mIsSelectedAtom = new boolean[mMol.getAllAtoms()];
                if (mDrawingObjectList != null) {
                    mIsSelectedObject = new boolean[mDrawingObjectList.size()];
                }

                mIsAddingToSelection = e.isShiftDown();
                for (int i = 0; i < mMol.getAllAtoms(); i++) {
                    mIsSelectedAtom[i] = mMol.isSelectedAtom(i);
                }
                if (mDrawingObjectList != null) {
                    for (int i = 0; i < mDrawingObjectList.size(); i++) {
                        mIsSelectedObject[i] = ((AbstractDrawingObject) mDrawingObjectList.get(i)).isSelected();
                    }
                }

                if (e.isControlDown()) {
                    mPendingRequest = cRequestSelectRect;
                } else {
                    mLassoRegion = new Polygon();
                    mLassoRegion.addPoint((int) mX1, (int) mY1);
                    mLassoRegion.addPoint((int) mX1, (int) mY1);
                    mPendingRequest = cRequestLassoSelect;
                }
                break;
            case JDrawToolbar.cToolDelete:
                storeState();
                deleteAt(mX1, mY1);
                break;
            case JDrawToolbar.cToolUnknownParity:
                int theAtom = mMol.findAtom(mX1, mY1);
                if (theAtom != -1) {
                    storeState();
                    mMol.setAtomConfigurationUnknown(theAtom, !mMol.isAtomConfigurationUnknown(theAtom));
                    fireMoleculeChanged();
                    update(UPDATE_REDRAW);
                }
                break;
            case JDrawToolbar.cToolESRAbs:
            case JDrawToolbar.cToolESRAnd:
            case JDrawToolbar.cToolESROr:
                if (mCurrentHiliteBond != -1 && qualifiesForESR(mCurrentHiliteBond)) {
                    storeState();
                    setESRInfo(mCurrentHiliteBond, (mCurrentTool == JDrawToolbar.cToolESRAbs) ?
                        Molecule.cESRTypeAbs :
                        (mCurrentTool == JDrawToolbar.cToolESRAnd) ?
                            Molecule.cESRTypeAnd : Molecule.cESRTypeOr);
                    fireMoleculeChanged();
                    update(UPDATE_REDRAW);
                }
                break;
            case JDrawToolbar.cToolStdBond:
            case JDrawToolbar.cToolUpBond:
            case JDrawToolbar.cToolDownBond:
                mAtom1 = mMol.findAtom(mX1, mY1);
                if (mAtom1 == -1) {
                    int bond = mMol.findBond(mX1, mY1);
                    if (bond != -1) {
                        storeState();
                        int bondType = Molecule.cBondTypeIncreaseOrder;
                        if (mCurrentTool == JDrawToolbar.cToolUpBond) {
                            bondType = Molecule.cBondTypeUp;
                        } else if (mCurrentTool == JDrawToolbar.cToolDownBond) {
                            bondType = Molecule.cBondTypeDown;
                        }
                        if (mMol.changeBond(bond, bondType)) {
                            fireMoleculeChanged();
                            update(UPDATE_REDRAW);
                        }
                        break;
                    }
                } else {
                    if (mMol.getAllConnAtoms(mAtom1) == MAX_CONNATOMS) {
                        return;
                    }
                    mX1 = mMol.getAtomX(mAtom1);
                    mY1 = mMol.getAtomY(mAtom1);
                }
                mPendingRequest = cRequestNewBond;
                suggestNewX2AndY2(mAtom1);
                repaint();
                break;
            case JDrawToolbar.cToolChain:
                mAtom1 = mMol.findAtom(mX1, mY1);
                if (mAtom1 != -1) {
                    if (mMol.getAllConnAtoms(mAtom1) == MAX_CONNATOMS) {
                        return;
                    }
                    mX1 = mMol.getAtomX(mAtom1);
                    mY1 = mMol.getAtomY(mAtom1);
                }
                mPendingRequest = cRequestNewChain;
                mChainAtoms = 0;
                break;
            case JDrawToolbar.cTool3Ring:
                storeState();
                if (mMol.addRing(mX1, mY1, 3, false)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cTool4Ring:
                storeState();
                if (mMol.addRing(mX1, mY1, 4, false)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cTool5Ring:
                storeState();
                if (mMol.addRing(mX1, mY1, 5, false)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cTool6Ring:
                storeState();
                if (mMol.addRing(mX1, mY1, 6, false)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cTool7Ring:
                storeState();
                if (mMol.addRing(mX1, mY1, 7, false)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAromRing:
                storeState();
                if (mMol.addRing(mX1, mY1, 6, true)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolPosCharge:
                storeState();
                if (mMol.changeAtomCharge(mX1, mY1, true)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolNegCharge:
                storeState();
                if (mMol.changeAtomCharge(mX1, mY1, false)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomH:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 1, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomC:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 6, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomN:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 7, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomO:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 8, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomSi:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 14, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomP:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 15, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomS:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 16, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomF:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 9, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomCl:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 17, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomBr:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 35, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomI:
                storeState();
                if (mMol.addOrChangeAtom(mX1, mY1, 53, 0, -1, 0)) {
                    fireMoleculeChanged();
                    update(UPDATE_CHECK_COORDS);
                }
                break;
            case JDrawToolbar.cToolAtomOther:
                if (mOtherAtom != -1) {
                    storeState();
                    if (mMol.addOrChangeAtom(mX1, mY1, mOtherAtom, mOtherMass, mOtherValence, mOtherRadical)) {
                        fireMoleculeChanged();
                        update(UPDATE_CHECK_COORDS);
                    }
                }
                break;
            case JDrawToolbar.cToolMapper:
                mAtom1 = mMol.findAtom(mX1, mY1);
                if (mAtom1 != -1 && mAtom1 < mMol.getAtoms()) {
                    mX1 = mMol.getAtomX(mAtom1);
                    mY1 = mMol.getAtomY(mAtom1);
                    mPendingRequest = cRequestMapAtoms;
                }
                break;
            case JDrawToolbar.cToolText:
                TextDrawingObject object = null;
                if (mCurrentHiliteObject == null) {
                    object = new TextDrawingObject();
                    object.setCoordinates(mX1, mY1);
                    mDrawingObjectList.add(object);
                } else if (mCurrentHiliteObject instanceof TextDrawingObject) {
                    object = (TextDrawingObject) mCurrentHiliteObject;
                }
                editTextObject(object);
                storeState();
                update(UPDATE_CHECK_COORDS);
                break;
        }
    }

    private void mouseReleasedButton1(MouseEvent e)
    {
        int pendingRequest = mPendingRequest;
        mPendingRequest = cRequestNone;
        switch (pendingRequest) {
            case cRequestNewBond:
                int stopAtom;

                stopAtom = mMol.findAtom(mX2, mY2);
                if (stopAtom != -1
                    && mMol.getAllConnAtoms(stopAtom) == MAX_CONNATOMS) {
                    return;
                }

                storeState();

                if (mAtom1 == -1) {
                    mAtom1 = mMol.addAtom(mX1, mY1);
                }
                if (stopAtom == -1) {
                    stopAtom = mMol.addAtom(mX2, mY2);
                }

                if ((mAtom1 != -1 && stopAtom != -1)) {
                    int bondType = Molecule.cBondTypeSingle;
                    if (mCurrentTool == JDrawToolbar.cToolUpBond) {
                        bondType = Molecule.cBondTypeUp;
                    }
                    if (mCurrentTool == JDrawToolbar.cToolDownBond) {
                        bondType = Molecule.cBondTypeDown;
                    }
                    mMol.addOrChangeBond(mAtom1, stopAtom, bondType);
                }

                fireMoleculeChanged();
                update(UPDATE_CHECK_COORDS);
                break;
            case cRequestNewChain:
                storeState();
                if (mChainAtoms > 0) {
                    if (mAtom1 == -1) {
                        mAtom1 = mMol.addAtom(mX1, mY1);
                    }

                    if (mChainAtom[0] == -1) {
                        mChainAtom[0] = mMol.addAtom(mChainAtomX[0],
                            mChainAtomY[0]);
                    }

                    if (mChainAtom[0] != -1) {
                        mMol.addBond(mAtom1, mChainAtom[0], Molecule.cBondTypeSingle);
                    }
                }

                if (mChainAtoms > 1) {
                    for (int i = 1; i < mChainAtoms; i++) {
                        if (mChainAtom[i] == -1) {
                            mChainAtom[i] = mMol.addAtom(mChainAtomX[i],
                                mChainAtomY[i]);
                        }
                        if (mChainAtom[i] != -1) {
                            mMol.addBond(mChainAtom[i - 1], mChainAtom[i], Molecule.cBondTypeSingle);
                        }
                    }
                }
                fireMoleculeChanged();
                update(UPDATE_CHECK_COORDS);
                break;
            case cRequestMoveSingle:
            case cRequestMoveSelected:
            case cRequestZoomAndRotate:
                fireMoleculeChanged();
            case cRequestMoveObject:
                update(UPDATE_CHECK_COORDS);
                break;
            case cRequestLassoSelect:
            case cRequestSelectRect:
                boolean selectionChanged = false;
                for (int i = 0; i < mMol.getAllAtoms(); i++) {
                    if (mIsSelectedAtom[i] != mMol.isSelectedAtom(i)) {
                        selectionChanged = true;
                        break;
                    }
                }
                if (selectionChanged) {
                    fireEvent(new DrawAreaEvent(this, DrawAreaEvent.TYPE_SELECTION_CHANGED, true));
                }
                repaint();
                break;
            case cRequestMapAtoms:
                boolean mapNoChanged = false;
                int atom2 = mCurrentHiliteAtom;
//                System.out.printf("Map Request Atom %d => %d (%d)\n", mAtom1, mAtom2, atom2);
                int mapNoAtom1 = mMol.getAtomMapNo(mAtom1);
                if (atom2 == -1) {
                    storeState();
                    if (mapNoAtom1 != 0) {
                        mapNoChanged = true;
                        for (int atom = 0; atom < mMol.getAtoms(); atom++) {
                            if (mMol.getAtomMapNo(atom) == mapNoAtom1) {
                                mMol.setAtomMapNo(atom, 0, false);
                            }
                        }
                        tryAutoMapReaction();
//					new MoleculeAutoMapper(mMol).autoMap();
                    }
                } else {
                    storeState();
                    mapNoChanged = true;

                    // If we only clicked on a mapped atom, then reset the map number
                    if (mAtom1 == atom2) {
                        int mapNo = mMol.getAtomMapNo(mAtom1);
                        for (int atom = 0; atom < mMol.getAtoms(); atom++) {
                            if (mMol.getAtomMapNo(atom) == mapNo) {
                                mMol.setAtomMapNo(atom, 0, false);
                            }
                        }
                    } else {
                        // remove old mapping numbers of atom1 and atom2
                        int mapNoAtom2 = mMol.getAtomMapNo(atom2);
                        for (int atom = 0; atom < mMol.getAtoms(); atom++) {
                            if (mMol.getAtomMapNo(atom) == mapNoAtom1
                                || mMol.getAtomMapNo(atom) == mapNoAtom2) {
                                mMol.setAtomMapNo(atom, 0, false);
                            }
                        }
                        int freeMapNo = 1;
                        for (int atom = 0; atom < mMol.getAtoms(); atom++) {
                            if (mMol.getAtomMapNo(atom) == freeMapNo) {
                                freeMapNo++;
                                atom = -1;
                            }
                        }
                        mMol.setAtomMapNo(mAtom1, freeMapNo, false);
                        mMol.setAtomMapNo(atom2, freeMapNo, false);

                        tryAutoMapReaction();
                    }

//				new MoleculeAutoMapper(mMol).autoMap();
                }

                if (mapNoChanged) {
                    fireMoleculeChanged();
                    mUpdateMode = Math.max(mUpdateMode, UPDATE_REDRAW);
                }

                repaint();
                break;
        }
    }

    static class MySSSearcher extends SSSearcher
    {
        @Override
        protected boolean areAtomsSimilar(int moleculeAtom, int fragmentAtom)
        {
            if (mMolecule.getAtomicNo(moleculeAtom) == mFragment.getAtomicNo(fragmentAtom))
                if(mMolecule.isAromaticAtom(moleculeAtom) || mFragment.isAromaticAtom(fragmentAtom))
                    return true;
            return super.areAtomsSimilar(moleculeAtom,fragmentAtom);
        }

        @Override
        protected boolean areBondsSimilar(int moleculeBond, int fragmentBond) {
            if (mMolecule.isAromaticBond(moleculeBond) || mMolecule.isDelocalizedBond(moleculeBond) ||
                mFragment.isAromaticBond(fragmentBond) || mFragment.isDelocalizedBond(fragmentBond)
                )
                return true;
            return super.areBondsSimilar(moleculeBond,fragmentBond);
            //return true;
        }
    }

    private void tryAutoMapReaction()
    {
        //				new MoleculeAutoMapper(mMol).autoMap();
        SSSearcher sss = new MySSSearcher();
        ReactionMapper mapper = new ReactionMapper();
        syncFragments();

        Reaction rxn = getReaction();//new Reaction(reaction);

        // Mark the manually mapped atoms, so we may re-assign them later
        for (int i = 0; i < rxn.getMolecules(); i++) {
            StereoMolecule mol = rxn.getMolecule(i);
            for (int a = 0; a < mol.getAtoms(); a++) {
                if (mol.getAtomMapNo(a) > 0) {
                    mol.setAtomicNo(a, FAKE_ATOM_NO + mol.getAtomMapNo(a));
                }
            }
        }
        rxn = mapper.matchReaction(rxn,sss);
        if (rxn != null) {
            int offset = 0;
            // Sync the display molecule with the reaction fragments
            {
                int[] fragmentAtom = new int[mFragment.length];
                for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
                    int fragment = mFragmentNo[atom];
                    if (mFragment[fragment].getAtomicNo(fragmentAtom[fragment]) > FAKE_ATOM_NO) {
                        mMol.setAtomMapNo(atom, mFragment[fragment].getAtomicNo(fragmentAtom[fragment]) - FAKE_ATOM_NO, false);
                        offset = Math.max(mMol.getAtomMapNo(atom), offset);
                    }
                    fragmentAtom[fragment]++;
                }
            }
            {
                int[] fragmentAtom = new int[mFragment.length];
                for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
                    int fragment = mFragmentNo[atom];
                    if (mFragment[fragment].getAtomMapNo(fragmentAtom[fragment]) > 0 && (mFragment[fragment].getAtomicNo(fragmentAtom[fragment]) <= FAKE_ATOM_NO)) {
                        mMol.setAtomMapNo(atom, mFragment[fragment].getAtomMapNo(fragmentAtom[fragment]) + offset, true);
                    }
                    fragmentAtom[fragment]++;
                }
            }
        }
        syncFragments();

//        mapper.resetFragments(mappedReaction);

//        AStarReactionMapper m = new AStarReactionMapper();
//        Reaction rxn = getReaction();
//        List<AStarReactionMapper.SlimMapping> l = m.map(rxn);
//        if (l != null && l.size() > 0) {
//            AStarReactionMapper.SlimMapping sm = l.get(0);
////            int[] mps = sm.getMapping();
////            for (int i = 0; i < mps.length; i++) {
////                System.out.printf("Maps %d -> %d\n", i, mps[i]);
////            }
//            m.activateMapping(sm);
////            for (int i = 0; i < mFragment.length; i++) {
////                StereoMolecule mol = mFragment[i];
////                for (int a = 0; a < mol.getAllAtoms(); a++) {
////                    System.out.printf("T Map %d = %d\n", a, mol.getAtomMapNo(a));
////                }
////            }
//
//            int[] fragmentAtom = new int[mFragment.length];
//            for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
//                int fragment = mFragmentNo[atom];
//                if (mMol.getAtomMapNo(atom) == 0)
//                    mMol.setAtomMapNo(atom, mFragment[fragment].getAtomMapNo(fragmentAtom[fragment]), true);
//                fragmentAtom[fragment]++;
//            }
//        }
    }

    /**
     * Checks whether this bond is a stereo bond and whether it refers to a
     * stereo center or BINAP bond, making it eligible for ESR information.
     *
     * @param stereoBond the up/down stereo bond
     * @return
     */
    private boolean qualifiesForESR(int stereoBond)
    {
        return mMol.isStereoBond(stereoBond) && (getESRAtom(stereoBond) != -1 || getESRBond(stereoBond) != -1);
    }

    /**
     * Locates the stereo center with parity 1 or 2 that is defined by the stereo bond.
     *
     * @param stereoBond
     * @return stereo center atom or -1 if no stereo center found
     */
    private int getESRAtom(int stereoBond)
    {
        int atom = mMol.getBondAtom(0, stereoBond);
        if (mMol.getAtomParity(atom) != Molecule.cAtomParityNone) {
            return (mMol.isAtomParityPseudo(atom)
                || (mMol.getAtomParity(atom) != Molecule.cAtomParity1
                && mMol.getAtomParity(atom) != Molecule.cAtomParity2)) ? -1 : atom;
        }
        if (mMol.getAtomPi(atom) == 1) {
            for (int i = 0; i < mMol.getConnAtoms(atom); i++) {
                if (mMol.getConnBondOrder(atom, i) == 2) {
                    int connAtom = mMol.getConnAtom(atom, i);
                    if (mMol.getAtomPi(connAtom) == 2
                        && (mMol.getAtomParity(connAtom) == Molecule.cAtomParity1
                        || mMol.getAtomParity(connAtom) == Molecule.cAtomParity2)) {
                        return connAtom;
                    }
                }
            }
        }
        return -1;
    }

    private int getESRBond(int stereoBond)
    {
        int bond = mMol.findBINAPChiralityBond(mMol.getBondAtom(0, stereoBond));
        if (bond != -1
            && mMol.getBondParity(bond) != Molecule.cBondParityEor1
            && mMol.getBondParity(bond) != Molecule.cBondParityZor2) {
            bond = -1;
        }

        return bond;
    }

    /**
     * Sets the ESR information of an atom or bond.
     *
     * @param atom atom to be changed or -1 if this applies to a bond
     * @param bond bond to be changed or -1 if this applies to an atom
     * @param type
     */
    private void setESRInfo(int stereoBond, int type)
    {
        int group = -1;

        int atom = getESRAtom(stereoBond);
        int bond = (atom == -1) ? getESRBond(stereoBond) : -1;

        // if type requires group information (type==And or type==Or)
        if (type != Molecule.cESRTypeAbs) {
            int maxGroup = -1;
            for (int i = 0; i < mMol.getAtoms(); i++) {
                if (i != atom
                    && mMol.getAtomESRType(i) == type
                    && (!mMol.isSelectedBond(stereoBond) || !mMol.isSelectedAtom(i))) {
                    int grp = mMol.getAtomESRGroup(i);
                    if (maxGroup < grp) {
                        maxGroup = grp;
                    }
                }
            }
            for (int i = 0; i < mMol.getBonds(); i++) {
                if (i != bond
                    && mMol.getBondESRType(i) == type
                    && (!mMol.isSelectedBond(stereoBond) || !mMol.isSelectedBond(i))) {
                    int grp = mMol.getBondESRGroup(i);
                    if (maxGroup < grp) {
                        maxGroup = grp;
                    }
                }
            }

            if ((atom == -1 ? mMol.getBondESRType(bond) : mMol.getAtomESRType(atom)) != type) {
                group = Math.min(maxGroup + 1, Molecule.cESRMaxGroups - 1);
            } else {
                group = (atom == -1) ? mMol.getBondESRGroup(bond) : mMol.getAtomESRGroup(atom);
                if (mMol.isSelectedBond(stereoBond)) {
                    boolean selectedShareOneGroup = true;
                    for (int i = 0; i < mMol.getAtoms(); i++) {
                        if (i != atom && mMol.isSelectedAtom(i)
                            && mMol.getAtomESRType(i) == type
                            && mMol.getAtomESRGroup(i) != group) {
                            selectedShareOneGroup = false;
                            break;
                        }
                    }
                    for (int i = 0; i < mMol.getBonds(); i++) {
                        if (i != bond && mMol.isSelectedBond(i)
                            && mMol.getBondESRType(i) == type
                            && mMol.getBondESRGroup(i) != group) {
                            selectedShareOneGroup = false;
                            break;
                        }
                    }
                    if (selectedShareOneGroup) {
                        if (group <= maxGroup) {
                            group++;
                            if (group == Molecule.cESRMaxGroups) {
                                group = 0;
                            }
                        } else {
                            group = 0;
                        }
                    }
                } else {
                    if (group <= maxGroup) {
                        group++;
                        if (group == Molecule.cESRMaxGroups) {
                            group = 0;
                        }
                    } else {
                        group = 0;
                    }
                }
            }
        }

        if (mMol.isSelectedBond(stereoBond)) {
            for (int i = 0; i < mMol.getBonds(); i++) {
                if (mMol.isSelectedBond(i) && mMol.isStereoBond(i)) {
                    int a = getESRAtom(i);
                    int b = getESRBond(i);
                    if (a != -1) {
                        mMol.setAtomESR(a, type, group);
                    } else if (b != -1) {
                        mMol.setBondESR(b, type, group);
                    }
                }
            }
        } else {
            if (atom != -1) {
                mMol.setAtomESR(atom, type, group);
            } else if (bond != -1) {
                mMol.setBondESR(bond, type, group);
            }
        }
    }

    private int findFragment(float x, float y)
    {
        int fragment = -1;
        float minDistance = Float.MAX_VALUE;
        for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
            float dx = mX1 - mMol.getAtomX(atom);
            float dy = mY1 - mMol.getAtomY(atom);
            float distance = (float) Math.sqrt(dx * dx + dy * dy);
            if (distance < FRAGMENT_MAX_CLICK_DISTANCE
                && minDistance > distance) {
                minDistance = distance;
                fragment = mFragmentNo[atom];
            }
        }

        return fragment;
    }

    /**
     * requires cHelperNeighbours
     *
     * @param atom
     */
    private void suggestNewX2AndY2(int atom)
    {
        float newAngle = (float) Math.PI * 2 / 3;
        if (atom != -1) {
            float angle[] = new float[MAX_CONNATOMS + 1];
            for (int i = 0; i < mMol.getAllConnAtoms(atom); i++) {
                angle[i] = mMol.getBondAngle(atom, mMol.getConnAtom(atom, i));
            }

            if (mMol.getAllConnAtoms(atom) == 1) {
                if (angle[0] < -Math.PI * 5 / 6) {
                    newAngle = (float) Math.PI / 3;
                } else if (angle[0] < -Math.PI / 2) {
                    newAngle = (float) Math.PI * 2 / 3;
                } else if (angle[0] < -Math.PI / 6) {
                    newAngle = (float) Math.PI / 3;
                } else if (angle[0] < 0.0) {
                    newAngle = (float) Math.PI * 2 / 3;
                } else if (angle[0] < Math.PI / 6) {
                    newAngle = -(float) Math.PI * 2 / 3;
                } else if (angle[0] < Math.PI / 2) {
                    newAngle = -(float) Math.PI / 3;
                } else if (angle[0] < Math.PI * 5 / 6) {
                    newAngle = -(float) Math.PI * 2 / 3;
                } else {
                    newAngle = -(float) Math.PI / 3;
                }
            } else {
                for (int i = mMol.getAllConnAtoms(atom) - 1; i > 0; i--) {    // bubble sort
                    for (int j = 0; j < i; j++) {
                        if (angle[j] > angle[j + 1]) {
                            float temp = angle[j];
                            angle[j] = angle[j + 1];
                            angle[j + 1] = temp;
                        }
                    }
                }
                angle[mMol.getAllConnAtoms(atom)] = angle[0] + (float) Math.PI * 2;

                int largestNo = 0;
                float largestDiff = 0.0f;
                for (int i = 0; i < mMol.getAllConnAtoms(atom); i++) {
                    float angleDiff = angle[i + 1] - angle[i];
                    if (largestDiff < angleDiff) {
                        largestDiff = angleDiff;
                        largestNo = i;
                    }
                }
                newAngle = (angle[largestNo] + angle[largestNo + 1]) / 2;
            }
        }
        float avbl = mMol.getAverageBondLength();
        mX2 = ((atom == -1) ? mX1 : mMol.getAtomX(atom)) + avbl * (float) Math.sin(newAngle);
        mY2 = ((atom == -1) ? mY1 : mMol.getAtomY(atom)) + avbl * (float) Math.cos(newAngle);
    }

    private boolean areAtomsMappingCompatible(int atom1, int atom2)
    {
        if (mMol.isFragment()) {
            int[] atomList1 = mMol.getAtomList(atom1);
            int[] atomList2 = mMol.getAtomList(atom2);
            if (atomList1 == null ^ atomList2 == null) {
                return false;
            }

            if (atomList1 != null) {
                if (atomList1.length != atomList2.length) {
                    return false;
                }
                for (int i = 0; i < atomList1.length; i++) {
                    if (atomList1[i] != atomList2[i]) {
                        return false;
                    }
                }
            }

            boolean isAny1 = ((mMol.getAtomQueryFeatures(atom1) & Molecule.cAtomQFAny) != 0);
            boolean isAny2 = ((mMol.getAtomQueryFeatures(atom2) & Molecule.cAtomQFAny) != 0);
            if (isAny1 != isAny2) {
                return false;
            }
        }

        return mMol.getAtomicNo(atom1) == mMol.getAtomicNo(atom2);
    }

    private boolean trackHiliting(float x, float y, boolean isDragging)
    {
        int theAtom = mMol.findAtom(x, y);
        int theBond = -1;

        if (isDragging
            && mPendingRequest == cRequestMapAtoms
            && theAtom != -1
            && (!areAtomsMappingCompatible(mAtom1, theAtom)
            || (mMol.getAtomMapNo(mAtom1) != 0 && mMol.getAtomMapNo(mAtom1) == mMol.getAtomMapNo(theAtom) && !mMol.isAutoMappedAtom(mAtom1))
            || shareSameReactionSide(mAtom1, theAtom))) {
            theAtom = -1;
        }

        if (theAtom != -1) {
            if (mCurrentTool == JDrawToolbar.cToolESRAbs
                || mCurrentTool == JDrawToolbar.cToolESRAnd
                || mCurrentTool == JDrawToolbar.cToolESROr) {
                theBond = mMol.getStereoBond(theAtom);
                theAtom = -1;
            } else if (mCurrentTool == JDrawToolbar.cToolMapper
                && theAtom >= mMol.getAtoms()) {
                theAtom = -1;
            }
        }

        if (theBond == -1
            && theAtom == -1
            && mCurrentTool != JDrawToolbar.cToolChain
            && mCurrentTool != JDrawToolbar.cToolMapper
            && mCurrentTool != JDrawToolbar.cToolUnknownParity
            && mCurrentTool != JDrawToolbar.cToolPosCharge
            && mCurrentTool != JDrawToolbar.cToolNegCharge
            && mCurrentTool != JDrawToolbar.cToolAtomH
            && mCurrentTool != JDrawToolbar.cToolAtomC
            && mCurrentTool != JDrawToolbar.cToolAtomN
            && mCurrentTool != JDrawToolbar.cToolAtomO
            && mCurrentTool != JDrawToolbar.cToolAtomSi
            && mCurrentTool != JDrawToolbar.cToolAtomP
            && mCurrentTool != JDrawToolbar.cToolAtomS
            && mCurrentTool != JDrawToolbar.cToolAtomF
            && mCurrentTool != JDrawToolbar.cToolAtomCl
            && mCurrentTool != JDrawToolbar.cToolAtomBr
            && mCurrentTool != JDrawToolbar.cToolAtomI
            && mCurrentTool != JDrawToolbar.cToolAtomOther) {
            theBond = mMol.findBond(x, y);
        }

        if (theBond != -1
            && (mCurrentTool == JDrawToolbar.cToolESRAbs
            || mCurrentTool == JDrawToolbar.cToolESRAnd
            || mCurrentTool == JDrawToolbar.cToolESROr)
            && !qualifiesForESR(theBond)) {
            theBond = -1;
        }

        // don't change object hiliting while dragging
        AbstractDrawingObject hiliteObject = mCurrentHiliteObject;
        if (!isDragging && mDrawingObjectList != null) {
            hiliteObject = null;
            if (theAtom == -1 && theBond == -1
                && (mCurrentTool == JDrawToolbar.cToolLassoPointer
                || mCurrentTool == JDrawToolbar.cToolDelete
                || mCurrentTool == JDrawToolbar.cToolText)) {
                for (AbstractDrawingObject theObject : mDrawingObjectList) {
                    if (mCurrentTool == JDrawToolbar.cToolLassoPointer
                        || (mCurrentTool == JDrawToolbar.cToolDelete && !(theObject instanceof ReactionArrow))
                        || (mCurrentTool == JDrawToolbar.cToolText && theObject instanceof TextDrawingObject)) {
                        if (theObject.checkHiliting(x, y)) {
                            hiliteObject = theObject;
                            if (mCurrentHiliteObject != null && mCurrentHiliteObject != theObject) {
                                mCurrentHiliteObject.clearHiliting();
                            }
                            break;
                        }
                    }
                }
            }
        }

        boolean repaintNeeded = (mCurrentHiliteAtom != theAtom
            || mCurrentHiliteBond != theBond
            || mCurrentHiliteObject != hiliteObject
            || hiliteObject != null);

        if (mCurrentHiliteAtom != theAtom) {
        	if (mCurrentHiliteAtom != -1 && mAtomKeyStrokeBuffer.length() != 0)
        		expandAtomKeyStrokes(mAtomKeyStrokeBuffer.toString());

            mCurrentHiliteAtom = theAtom;
            mAtomKeyStrokeBuffer.setLength(0);
            fireEvent(new DrawAreaEvent(this, DrawAreaEvent.TYPE_HILITE_ATOM_CHANGED, true));
        }
        if (mCurrentHiliteBond != theBond) {
            mCurrentHiliteBond = theBond;
            fireEvent(new DrawAreaEvent(this, DrawAreaEvent.TYPE_HILITE_BOND_CHANGED, true));
        }
        mCurrentHiliteObject = hiliteObject;

        return repaintNeeded;
    }

    private int getAtomKeyStrokeValidity(String s) {
    	if (Molecule.getAtomicNoFromLabel(s) != 0)
    		return KEY_IS_ATOM_LABEL;
    	if (NamedSubstituents.getSubstituentIDCode(s) != null)
    		return KEY_IS_SUBSTITUENT;
    	if (isValidAtomKeyStrokeStart(s))
    		return KEY_IS_VALID_START;
    	return KEY_IS_INVALID;
    }

    /**
     * @param s
     * @return true if s is either a valid atom symbol or a valid substituent name
     */
    private boolean isValidAtomKeyStroke(String s) {
    	return Molecule.getAtomicNoFromLabel(s) != 0
   			|| NamedSubstituents.getSubstituentIDCode(s) != null;
   	}

    /**
     * @param s
     * @return true if adding one or more chars may still create a valid key stroke sequence
     */
    private boolean isValidAtomKeyStrokeStart(String s) {
    	if (s.length() < 3)
			for (int i=1; i<Molecule.cAtomLabel.length; i++)
				if (Molecule.cAtomLabel[i].startsWith(s))
					return true;

    	return NamedSubstituents.isValidSubstituentNameStart(s);
   	}

    private void expandAtomKeyStrokes(String keyStrokes) {
    	mAtomKeyStrokeBuffer.setLength(0);

    	int atomicNo = Molecule.getAtomicNoFromLabel(keyStrokes);
        if (atomicNo != 0) {
            storeState();
            if (mMol.changeAtom(mCurrentHiliteAtom, atomicNo, 0, -1, 0)) {
                fireMoleculeChanged();
                update(UPDATE_CHECK_COORDS);
                return;
            }
        }

        StereoMolecule substituent = NamedSubstituents.getSubstituent(keyStrokes);
        if (substituent != null) {
            storeState();

            // Copy the the fragment containing the attachment point into a new molecule.
            // Then attach the substituent, create new atom coordinates for the substituent,
            // while retaining coordinates of the fragment.
        	StereoMolecule fragment = new StereoMolecule();
        	fragment.addFragment(mMol, mCurrentHiliteAtom, null);
        	float sourceAVBL = fragment.getAverageBondLength();
            int firstAtomInFragment = fragment.getAllAtoms();
        	for (int atom=0; atom<fragment.getAllAtoms(); atom++)
        		fragment.setAtomMarker(atom, true);
        	fragment.addSubstituent(substituent, 0);
            new CoordinateInventor(CoordinateInventor.MODE_KEEP_MARKED_ATOM_COORDS).invent(fragment);

            float dx = mMol.getAtomX(mCurrentHiliteAtom) - sourceAVBL * fragment.getAtomX(0);
            float dy = mMol.getAtomY(mCurrentHiliteAtom) - sourceAVBL * fragment.getAtomY(0);

            // Attach the substituent to the complete molecule and take coodinates from the
            // previously created fragment-substituent species.
            int firstAtomInMol = mMol.getAllAtoms();
            mMol.addSubstituent(substituent, mCurrentHiliteAtom);
            int substituentAtoms = mMol.getAllAtoms() - firstAtomInMol;
        	for (int i=0; i<substituentAtoms; i++) {
        		mMol.setAtomX(firstAtomInMol+i, sourceAVBL * fragment.getAtomX(firstAtomInFragment+i) + dx);
        		mMol.setAtomY(firstAtomInMol+i, sourceAVBL * fragment.getAtomY(firstAtomInFragment+i) + dy);
        		}
            mMol.setStereoBondsFromParity();

        	fireMoleculeChanged();
            update(UPDATE_CHECK_COORDS);
        }
    }

    private AbstractDrawingObject findDrawingObject(float x, float y, String type, boolean forDeletion)
    {
        if (mDrawingObjectList != null) {
            for (AbstractDrawingObject drawingObject : mDrawingObjectList) {
                if ((type == null || type.equals(drawingObject.getTypeString())
                    && !forDeletion || drawingObject.isDeletable())
                    && drawingObject.contains(x, y)) {
                    return drawingObject;
                }
            }
        }

        return null;
    }

    private void editTextObject(TextDrawingObject object)
    {
        Component c = this;
        while (c.getParent() != null) {
            c = c.getParent();
        }
        new JTextDrawingObjectDialog((Frame) c, object);

        boolean nonWhiteSpaceFound = false;
        for (int i = 0; i < object.getText().length(); i++) {
            if (!Character.isWhitespace(object.getText().charAt(i))) {
                nonWhiteSpaceFound = true;
                break;
            }
        }
        if (!nonWhiteSpaceFound) {
            mDrawingObjectList.remove(object);
        }

        repaint();
    }

    private boolean shareSameReactionSide(int atom1, int atom2)
    {
        ReactionArrow arrow = (ReactionArrow) mDrawingObjectList.get(0);
        return !(arrow.isOnProductSide(mMol.getAtomX(atom1), mMol.getAtomY(atom1))
            ^ arrow.isOnProductSide(mMol.getAtomX(atom2), mMol.getAtomY(atom2)));
    }

    protected void restoreState()
    {
        if (mUndoMol == null) {
            return;
        }
        mUndoMol.copyMolecule(mMol);
        mDrawingObjectList = (mUndoDrawingObjectList == null) ?
            null : new DrawingObjectList(mUndoDrawingObjectList);
    }

    protected void storeState()
    {
        if (mUndoMol == null) {
            mUndoMol = new Molecule();
        }
        mMol.copyMolecule(mUndoMol);

        mUndoDrawingObjectList = (mDrawingObjectList == null) ?
            null : new DrawingObjectList(mDrawingObjectList);
    }

    private boolean deleteHilited()
    {
        if (mCurrentHiliteAtom != -1) {
            mMol.deleteAtom(mCurrentHiliteAtom);
            mCurrentHiliteAtom = -1;
            fireMoleculeChanged();
            update(UPDATE_REDRAW);
            return true;
        }

        if (mCurrentHiliteBond != -1) {
            mMol.deleteBondAndSurrounding(mCurrentHiliteBond);
            mCurrentHiliteBond = -1;
            fireMoleculeChanged();
            update(UPDATE_REDRAW);
            return true;
        }

        if (mCurrentHiliteObject != null && mCurrentHiliteObject.isDeletable()) {
            mDrawingObjectList.remove(mCurrentHiliteObject);
            mCurrentHiliteObject = null;
            update(UPDATE_REDRAW);
            return true;
        }
        return false;
    }

    private boolean deleteAt(float x, float y)
    {
        if (mMol.deleteAtomOrBond(x, y)) {
            fireMoleculeChanged();
            update(UPDATE_REDRAW);
            return true;
        }
        AbstractDrawingObject drawingObject = findDrawingObject(x, y, null, true);
        if (drawingObject != null) {
            mDrawingObjectList.remove(drawingObject);
            mCurrentHiliteObject = null;
            update(UPDATE_REDRAW);
            return true;
        }
        return false;
    }

    private void duplicateSelected()
    {
        int atomCount = 0;
        for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
            if (mMol.isSelectedAtom(atom)) {
                atomCount++;
            }
        }
//		int bondCount = 0;
//		for (int bond=0; bond<mMol.getAllBonds(); bond++)
//			if (mMol.isSelectedBond(bond))
//				bondCount++;

        int originalAtoms = mMol.getAllAtoms();
        int originalBonds = mMol.getAllBonds();

        mX = Arrays.copyOf(mX, mX.length + atomCount);
        mY = Arrays.copyOf(mY, mY.length + atomCount);
        int[] atomMap = new int[mMol.getAllAtoms()];
        int esrGroupCountAND = mMol.renumberESRGroups(Molecule.cESRTypeAnd);
        int esrGroupCountOR = mMol.renumberESRGroups(Molecule.cESRTypeOr);
        for (int atom = 0; atom < originalAtoms; atom++) {
            if (mMol.isSelectedAtom(atom)) {
                int newAtom = mMol.getAllAtoms();
                mX[newAtom] = mX[atom];
                mY[newAtom] = mY[atom];
                atomMap[atom] = newAtom;
                mMol.copyAtom(mMol, atom, esrGroupCountAND, esrGroupCountOR);
            }
        }
        for (int bond = 0; bond < originalBonds; bond++) {
            if (mMol.isSelectedBond(bond)) {
                mMol.copyBond(mMol, bond, esrGroupCountAND, esrGroupCountOR, atomMap, false);
            }
        }
        for (int atom = 0; atom < originalAtoms; atom++) {
            mMol.setAtomSelection(atom, false);
        }
        for (int atom = originalAtoms; atom < mMol.getAllAtoms(); atom++) {
            mMol.setAtomMapNo(atom, 0, false);
        }

        if (mDrawingObjectList != null) {
            for (int i = mDrawingObjectList.size() - 1; i >= 0; i--) {
                AbstractDrawingObject object = (AbstractDrawingObject) mDrawingObjectList.get(i);
                if (object.isSelected() && !(object instanceof ReactionArrow)) {
                    mDrawingObjectList.add(object.clone());
                }
            }
        }
    }

    private void fireMoleculeChanged()
    {
        fireEvent(new DrawAreaEvent(this, DrawAreaEvent.TYPE_MOLECULE_CHANGED, true));
    }

    private void fireEvent(DrawAreaEvent e)
    {
        for (DrawAreaListener l : mListeners) {
            l.contentChanged(e);
        }
    }

    /**
     * Use this to inform the JDrawArea after changing its molecule from outside.
     */
    public void moleculeChanged()
    {
        moleculeChanged(false);
    }

    /**
     * Ideally don't use this from outside JDrawArea. Use moleculeChanged() instead.
     *
     * @param userChange is true if the change was done within the editor
     */
    public void moleculeChanged(boolean userChange)
    {
        fireEvent(new DrawAreaEvent(this, DrawAreaEvent.TYPE_MOLECULE_CHANGED, userChange));
        update(UPDATE_SCALE_COORDS);
    }

    public StereoMolecule getMolecule()
    {
        return mMol;
    }

    public void setMolecule(StereoMolecule theMolecule)
    {
        if (mMol == theMolecule) {
            return;
        }
        mMol = theMolecule;
        storeState();
        moleculeChanged(false);
    }

    public StereoMolecule[] getFragments()
    {
        return mFragment;
    }

    public void setFragments(StereoMolecule[] fragment)
    {
        mMol.deleteMolecule();
        mFragment = fragment;
        for (int i = 0; i < fragment.length; i++) {
            mMol.addMolecule(mFragment[i]);
        }
        storeState();

        mFragmentNo = new int[mMol.getAllAtoms()];
        for (int atom = 0, f = 0; f < mFragment.length; f++) {
            for (int j = 0; j < mFragment[f].getAllAtoms(); j++) {
                mFragmentNo[atom++] = f;
            }
        }

        fireEvent(new DrawAreaEvent(this, DrawAreaEvent.TYPE_MOLECULE_CHANGED, false));

        mMode = MODE_MULTIPLE_FRAGMENTS;
        update(UPDATE_SCALE_COORDS_USE_FRAGMENTS);
    }

    /**
     * @return mapped reaction with absolute coordinates, but without drawing objects
     */
    public Reaction getReaction()
    {
        if ((mMode & MODE_REACTION) == 0) {
            return null;
        }

        Reaction rxn = new Reaction();
        for (int i = 0; i < mFragment.length; i++) {
            if (i < mReactantCount) {
                rxn.addReactant(mFragment[i]);
            } else {
                rxn.addProduct(mFragment[i]);
            }
        }
        return rxn;
    }

    /**
     * @return mapped reaction with absolute coordinates and drawing objects
     */
    public Reaction getReactionAndDrawings()
    {
        Reaction rxn = getReaction();
        if (rxn != null) {
            rxn.setDrawingObjects(getDrawingObjects());
        }
        return rxn;
    }

    public void setReaction(Reaction rxn)
    {
        mMol.deleteMolecule();
        mFragment = new StereoMolecule[rxn.getMolecules()];
        mReactantCount = rxn.getReactants();
        boolean isFragment = false;
        for (int i = 0; i < rxn.getMolecules(); i++) {
            isFragment |= rxn.getMolecule(i).isFragment();
            mFragment[i] = rxn.getMolecule(i);
            mMol.addMolecule(mFragment[i]);
        }
        mMol.setFragment(isFragment);
        storeState();

        mFragmentNo = new int[mMol.getAllAtoms()];
        for (int atom = 0, f = 0; f < mFragment.length; f++) {
            for (int j = 0; j < mFragment[f].getAllAtoms(); j++) {
                mFragmentNo[atom++] = f;
            }
        }

        fireEvent(new DrawAreaEvent(this, DrawAreaEvent.TYPE_MOLECULE_CHANGED, false));

        mMode = MODE_MULTIPLE_FRAGMENTS | MODE_REACTION;
        update(UPDATE_SCALE_COORDS_USE_FRAGMENTS);
    }

    public MarkushStructure getMarkushStructure()
    {
        if ((mMode & MODE_MARKUSH_STRUCTURE) == 0) {
            return null;
        }

        MarkushStructure markush = new MarkushStructure();
        for (int i = 0; i < mFragment.length; i++) {
            if (i < mReactantCount) {
                markush.addCore(mFragment[i]);
            } else {
                markush.addRGroup(mFragment[i]);
            }
        }
        return markush;
    }

    public void setMarkushStructure(MarkushStructure markush)
    {
        mMol.deleteMolecule();
        mFragment = new StereoMolecule[markush.getCoreCount() + markush.getRGroupCount()];
        mReactantCount = markush.getCoreCount();
        boolean isFragment = false;
        for (int i = 0; i < markush.getCoreCount() + markush.getRGroupCount(); i++) {
            mFragment[i] = (i < markush.getCoreCount()) ? markush.getCoreStructure(i)
                : markush.getRGroup(i - markush.getCoreCount());
            isFragment |= mFragment[i].isFragment();
            mMol.addMolecule(mFragment[i]);
        }
        mMol.setFragment(isFragment);
        storeState();

        mFragmentNo = new int[mMol.getAllAtoms()];
        for (int atom = 0, f = 0; f < mFragment.length; f++) {
            for (int j = 0; j < mFragment[f].getAllAtoms(); j++) {
                mFragmentNo[atom++] = f;
            }
        }

        fireEvent(new DrawAreaEvent(this, DrawAreaEvent.TYPE_MOLECULE_CHANGED, false));

        mMode = MODE_MULTIPLE_FRAGMENTS | MODE_MARKUSH_STRUCTURE;
        update(UPDATE_SCALE_COORDS_USE_FRAGMENTS);
    }

    public void setDisplayMode(int dMode)
    {
        mDisplayMode = dMode;
        update(UPDATE_REDRAW);
    }

    /**
     * Defines additional atom text to be displayed in top right
     * position of some/all atom label. If the atom is charged, then
     * the atom text follows the charge information.
     * If using atom text make sure to update it accordingly, if atom
     * indexes change due to molecule changes.
     * Atom text is not supported for MODE_REACTION, MODE_MULTIPLE_FRAGMENTS or MODE_MARKUSH_STRUCTURE.
     *
     * @param null or String array matching atom indexes (may contain null entries)
     */
    public void setAtomText(String[] atomText)
    {
        mAtomText = atomText;
    }

    public DrawingObjectList getDrawingObjects()
    {
        return mDrawingObjectList;
    }

    public void setDrawingObjects(DrawingObjectList drawingObjectList)
    {
        mDrawingObjectList = drawingObjectList;
        storeState();
        update(UPDATE_SCALE_COORDS);
    }

    // Added by CXR 
    public int getMode()
    {
        return mMode;
    }

    public int getHiliteAtom()
    {
        return mCurrentHiliteAtom;
    }

    public int getHiliteBond()
    {
        return mCurrentHiliteBond;
    }

    public void setHiliteBondSet(int[] bondSet)
    {
        mHiliteBondSet = bondSet;
        update(UPDATE_REDRAW);
    }

    public void setReactionMode(boolean rxn)
    {
        if (rxn) {
            Molecule m[] = this.getFragments();
            if (m == null) {
                setReaction(new Reaction(new StereoMolecule[]{new StereoMolecule(this.getMolecule())}, 1));
            } else {
                mMode = MODE_MULTIPLE_FRAGMENTS | MODE_REACTION;
                Reaction r = getReaction();
                setReaction(r);
            }
        } else {
            mMode &= ~MODE_REACTION;
        }
    }

    // CXR Need this method for derived components
    protected void setUpdateMode(int mode)
    {
        mUpdateMode = mode;
    }

    public boolean isAtomColorSupported()
    {
        return mAtomColorSupported;
    }

    public void setAtomColorSupported(boolean acs)
    {
        mAtomColorSupported = acs;
    }

    private void cleanupCoordinates(Graphics g, ExtendedDepictor depictor)
    {
        int selectedAtomCount = 0;
        for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
            if (mMol.isSelectedAtom(atom)) {
                selectedAtomCount++;
            }
        }
        boolean selectedOnly = (selectedAtomCount != 0 && selectedAtomCount != mMol.getAllAtoms());

        if ((mMode & MODE_MULTIPLE_FRAGMENTS) == 0) {
            cleanupMoleculeCoordinates(g, depictor, selectedOnly);
        } else {
            cleanupMultiFragmentCoordinates(g, depictor, selectedOnly);
        }

        if (selectedOnly)
        	mMol.removeAtomMarkers();
    }

    private void cleanupMoleculeCoordinates(Graphics g, ExtendedDepictor depictor, boolean selectedOnly)
    {
        if (mUpdateMode == UPDATE_INVENT_COORDS) {
            if (selectedOnly) {
                for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
                    mMol.setAtomMarker(atom, !mMol.isSelectedAtom(atom));
                }
            }

            new CoordinateInventor(selectedOnly ? CoordinateInventor.MODE_KEEP_MARKED_ATOM_COORDS : 0).invent(mMol);
            mMol.setStereoBondsFromParity();
        }

        depictor.updateCoords(g, new Rectangle2D.Float(0, 0, getWidth(), getHeight()),
            AbstractDepictor.cModeInflateToMaxAVBL);
    }

    private void cleanupMultiFragmentCoordinates(Graphics g, ExtendedDepictor depictor, boolean selectedOnly)
    {
        if (selectedOnly && mUpdateMode == UPDATE_INVENT_COORDS) {
            int[] fragmentAtom = new int[mFragment.length];
            for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
                int fragment = mFragmentNo[atom];
                mFragment[fragment].setAtomMarker(fragmentAtom[fragment], !mMol.isSelectedAtom(atom));
                fragmentAtom[fragment]++;
            }
        }

        Rectangle2D.Float[] boundingRect = new Rectangle2D.Float[mFragment.length];
//		float fragmentWidth = 0.0f;
        for (int fragment = 0; fragment < mFragment.length; fragment++) {
            if (mUpdateMode == UPDATE_INVENT_COORDS) {
                new CoordinateInventor(selectedOnly ? CoordinateInventor.MODE_KEEP_MARKED_ATOM_COORDS : 0).invent(mFragment[fragment]);
                mFragment[fragment].setStereoBondsFromParity();
            }
            Depictor d = new Depictor(mFragment[fragment]);
            d.updateCoords(g, null, AbstractDepictor.cModeInflateToMaxAVBL);
            boundingRect[fragment] = d.getBoundingRect();
//			fragmentWidth += boundingRect[fragment].width;
        }

        float spacing = FRAGMENT_CLEANUP_DISTANCE * AbstractDepictor.cOptAvBondLen;
        float avbl = mMol.getAverageBondLength();
        float arrowWidth = ((mMode & MODE_REACTION) == 0) ?
            0f
            : (mUpdateMode == UPDATE_SCALE_COORDS_USE_FRAGMENTS) ?
            DEFAULT_ARROW_LENGTH * getWidth()
            : ((ReactionArrow) mDrawingObjectList.get(0)).getLength() * AbstractDepictor.cOptAvBondLen / avbl;

        float rawX = 0.5f * spacing;
        for (int fragment = 0; fragment <= mFragment.length; fragment++) {
            if ((mMode & MODE_REACTION) != 0 && fragment == mReactantCount) {
                ((ReactionArrow) mDrawingObjectList.get(0)).setCoordinates(
                    rawX - spacing / 2, getHeight() / 2, rawX - spacing / 2 + arrowWidth, getHeight() / 2);
                rawX += arrowWidth;
            }

            if (fragment == mFragment.length) {
                break;
            }

            float dx = rawX - boundingRect[fragment].x;
            float dy = 0.5f * (getHeight() - boundingRect[fragment].height)
                - boundingRect[fragment].y;
            mFragment[fragment].translateCoords(dx, dy);

            rawX += spacing + boundingRect[fragment].width;
        }

        depictor.updateCoords(g, new Rectangle2D.Float(0, 0, getWidth(), getHeight()),
            AbstractDepictor.cModeInflateToMaxAVBL);

        int[] fragmentAtom = new int[mFragment.length];
        for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
            int fragment = mFragmentNo[atom];
            mMol.setAtomX(atom, mFragment[fragment].getAtomX(fragmentAtom[fragment]));
            mMol.setAtomY(atom, mFragment[fragment].getAtomY(fragmentAtom[fragment]));

            fragmentAtom[fragment]++;
        }

        mMol.setStereoBondsFromParity();
    }

    private void analyzeFragmentMembership()
    {
        mMol.ensureHelperArrays(Molecule.cHelperParities);

        int[] fragmentNo = new int[mMol.getAllAtoms()];
        int fragments = mMol.getFragmentNumbers(fragmentNo, false);

        fragments = joinCloseFragments(fragmentNo, fragments);
        sortFragmentsByPosition(fragmentNo, fragments);
        mFragmentNo = fragmentNo;

        mFragment = mMol.getFragments(fragmentNo, fragments);
    }

    private void syncFragments()
    {
        mMol.ensureHelperArrays(Molecule.cHelperParities);

        int[] fragmentNo = new int[mMol.getAllAtoms()];
        int fragments = mMol.getFragmentNumbers(fragmentNo, false);

//        fragments = joinCloseFragments(fragmentNo, fragments);
        sortFragmentsByPosition(fragmentNo, fragments);
        mFragmentNo = fragmentNo;

        mFragment = mMol.getFragments(fragmentNo, fragments);
        for (StereoMolecule m : mFragment) {
            m.ensureHelperArrays(Molecule.cHelperParities);
        }
    }

    private int joinCloseFragments(int[] fragmentNo, int fragments)
    {
        if (fragments < 2) {
            return fragments;
        }

        boolean[][] mergeFragments = new boolean[fragments][];
        for (int i = 1; i < fragments; i++) {
            mergeFragments[i] = new boolean[i];
        }

        float avbl = mMol.getAverageBondLength();
        for (int atom1 = 1; atom1 < mMol.getAllAtoms(); atom1++) {
            for (int atom2 = 0; atom2 < atom1; atom2++) {
                float dx = mMol.getAtomX(atom2) - mMol.getAtomX(atom1);
                float dy = mMol.getAtomY(atom2) - mMol.getAtomY(atom1);
                float distance = (float) Math.sqrt(dx * dx + dy * dy);
                if (distance < FRAGMENT_GROUPING_DISTANCE * avbl) {
                    int fragment1 = fragmentNo[atom1];
                    int fragment2 = fragmentNo[atom2];
                    if (fragment1 != fragment2) {
                        if (fragment1 > fragment2) {
                            mergeFragments[fragment1][fragment2] = true;
                        } else {
                            mergeFragments[fragment2][fragment1] = true;
                        }
                    }
                }
            }
        }

        int[] newFragmentIndex = new int[fragments];
        for (int fragment = 0; fragment < fragments; fragment++) {
            newFragmentIndex[fragment] = fragment;
        }

        int mergeCount = 0;
        for (int i = 1; i < fragments; i++) {
            for (int j = 0; j < i; j++) {
                if (mergeFragments[i][j]) {
                    int index1 = newFragmentIndex[i];
                    int index2 = newFragmentIndex[j];
                    if (index1 != index2) {
                        mergeCount++;
                        int minIndex = Math.min(index1, index2);
                        int maxIndex = Math.max(index1, index2);
                        for (int k = 0; k < fragments; k++) {
                            if (newFragmentIndex[k] == maxIndex) {
                                newFragmentIndex[k] = minIndex;
                            } else if (newFragmentIndex[k] > maxIndex) {
                                newFragmentIndex[k]--;
                            }
                        }
                    }
                }
            }
        }

        for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
            fragmentNo[atom] = newFragmentIndex[fragmentNo[atom]];
        }

        return fragments - mergeCount;
    }

    private void sortFragmentsByPosition(int[] fragmentNo, int fragments)
    {
        int[][] fragmentDescriptor = new int[fragments][((mMode & (MODE_REACTION | MODE_MARKUSH_STRUCTURE)) != 0) ? 2 : 1];
        for (int fragment = 0; fragment < fragments; fragment++) {
            fragmentDescriptor[fragment][0] = fragment;
        }

        Point[] fragmentCOG = calculateFragmentCenterOfGravity(fragmentNo, fragments);

        if ((mMode & MODE_REACTION) != 0) {
            mReactantCount = 0;
            ReactionArrow arrow = ((mMode & MODE_REACTION) != 0) ? (ReactionArrow) mDrawingObjectList.get(0) : null;
            for (int fragment = 0; fragment < fragments; fragment++) {
                fragmentDescriptor[fragment][1] = (arrow.isOnProductSide(fragmentCOG[fragment].x,
                    fragmentCOG[fragment].y)) ? 1 : 0;
                if (fragmentDescriptor[fragment][1] == 0) {
                    mReactantCount++;
                }
            }
        } else if ((mMode & MODE_MARKUSH_STRUCTURE) != 0) {
            mReactantCount = fragments;
            for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
                if (mMol.getAtomicNo(atom) == 0 && fragmentDescriptor[fragmentNo[atom]][1] == 0) {
                    fragmentDescriptor[fragmentNo[atom]][1] = 1;
                    mReactantCount--;
                }
            }
        }

        final Point[] cog = fragmentCOG;
        Arrays.sort(fragmentDescriptor, new Comparator<int[]>()
        {
            public int compare(int[] fragmentDescriptor1, int[] fragmentDescriptor2)
            {
                if ((mMode & (MODE_REACTION | MODE_MARKUSH_STRUCTURE)) != 0) {
                    if (fragmentDescriptor1[1] != fragmentDescriptor2[1]) {
                        return (fragmentDescriptor1[1] == 0) ? -1 : 1;
                    }
                }

                return (cog[fragmentDescriptor1[0]].x
                    + cog[fragmentDescriptor1[0]].y
                    < cog[fragmentDescriptor2[0]].x
                    + cog[fragmentDescriptor2[0]].y) ? -1 : 1;
            }
        });

        int[] newFragmentIndex = new int[fragments];
        Point[] centerOfGravity = new Point[fragments];
        for (int fragment = 0; fragment < fragments; fragment++) {
            int oldIndex = ((int[]) fragmentDescriptor[fragment])[0];
            newFragmentIndex[oldIndex] = fragment;
            centerOfGravity[fragment] = fragmentCOG[oldIndex];
        }

        fragmentCOG = centerOfGravity;
        for (int atom1 = 0; atom1 < mMol.getAllAtoms(); atom1++) {
            fragmentNo[atom1] = newFragmentIndex[fragmentNo[atom1]];
        }
    }

    private Point[] calculateFragmentCenterOfGravity(int[] fragmentNo, int fragments)
    {
        Point[] fragmentCOG = new Point[fragments];
        int[] fragmentAtoms = new int[fragments];
        for (int fragment = 0; fragment < fragments; fragment++) {
            fragmentCOG[fragment] = new Point(0, 0);
        }
        for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
            fragmentCOG[fragmentNo[atom]].x += mMol.getAtomX(atom);
            fragmentCOG[fragmentNo[atom]].y += mMol.getAtomY(atom);
            fragmentAtoms[fragmentNo[atom]]++;
        }
        for (int fragment = 0; fragment < fragments; fragment++) {
            fragmentCOG[fragment].x /= fragmentAtoms[fragment];
            fragmentCOG[fragment].y /= fragmentAtoms[fragment];
        }
        return fragmentCOG;
    }

    private void updateCursor()
    {
        int cursor = -1;
        switch (mCurrentTool) {
            case JDrawToolbar.cToolZoom:
                cursor = CursorHelper.cZoomCursor;
                break;
            case JDrawToolbar.cToolLassoPointer:
                if ((mCurrentHiliteAtom != -1 && mMol.isSelectedAtom(mCurrentHiliteAtom))
                    || (mCurrentHiliteBond != -1 && mMol.isSelectedBond(mCurrentHiliteBond))) {
                    cursor = mMouseIsDown ? CursorHelper.cFistCursor
                        : mShiftIsDown ? CursorHelper.cHandPlusCursor
                        : CursorHelper.cHandCursor;
                } else if (mCurrentHiliteAtom != -1
                    || mCurrentHiliteBond != -1) {
                    cursor = CursorHelper.cPointerCursor;
                } else if (mCurrentHiliteObject != null) {
                    cursor = mMouseIsDown ? CursorHelper.cFistCursor
                        : (mShiftIsDown
                        && !(mCurrentHiliteObject instanceof ReactionArrow)) ?
                        CursorHelper.cHandPlusCursor : CursorHelper.cHandCursor;
                } else {
                    cursor = mShiftIsDown ?
                        (mControlIsDown ? CursorHelper.cSelectRectPlusCursor : CursorHelper.cLassoPlusCursor)
                        : (mControlIsDown ? CursorHelper.cSelectRectCursor : CursorHelper.cLassoCursor);
                }
                break;
            case JDrawToolbar.cToolDelete:
                cursor = CursorHelper.cDeleteCursor;
                break;
            case JDrawToolbar.cToolChain:
                cursor = CursorHelper.cChainCursor;
                break;
            case JDrawToolbar.cToolText:
                cursor = CursorHelper.cTextCursor;
                break;
            default:
                cursor = CursorHelper.cPointerCursor;
                break;
        }

        if (mCurrentCursor != cursor) {
            mCurrentCursor = cursor;
            setCursor(CursorHelper.getCursor(cursor));
        }
    }

    private void initializeDragAndDrop(int dropAction)
    {
        if (dropAction != DnDConstants.ACTION_NONE) {
            MoleculeDropAdapter d = new MoleculeDropAdapter()
            {
                public void onDropMolecule(StereoMolecule m, Point pt)
                {
                    if (m != null && m.getAllAtoms() != 0) {
                        for (int atom = 0; atom < mMol.getAllAtoms(); atom++) {
                            mMol.setAtomSelection(atom, false);                // deselect all current atoms
                        }

                        float avbl = mMol.getAverageBondLength();
                        float dropAVBL = m.getAverageBondLength();
                        m.scaleCoords(avbl / dropAVBL);
                        Point cog = new Point();
                        for (int atom = 0; atom < m.getAllAtoms(); atom++) {
                            m.setAtomSelection(atom, true);                    // select all new atoms
                            cog.x += m.getAtomX(atom);
                            cog.y += m.getAtomY(atom);
                        }
                        cog.x /= m.getAllAtoms();
                        cog.y /= m.getAllAtoms();
                        m.translateCoords(pt.x - cog.x, pt.y - cog.y);
                        m.removeAtomColors();
                        m.removeBondHiliting();
                        mMol.addMolecule(m);
                        fireMoleculeChanged();
                        update(UPDATE_CHECK_COORDS);
                    }
                }
            };

            new DropTarget(this, dropAction, d, true, new OurFlavorMap());
        }
    }

    // This class is needed for inter-jvm drag&drop. Although not neccessary for standard environments, it prevents
// nasty "no native data was transfered" errors. It still might create ClassNotFoundException in the first place by
// the SystemFlavorMap, but as I found it does not hurt, since the context classloader will be installed after
// the first call. I know, that this depends heavely on a specific behaviour of the systemflavormap, but for now
// there's nothing I can do about it.
    static class OurFlavorMap implements java.awt.datatransfer.FlavorMap
    {
        @Override
        public java.util.Map<DataFlavor, String> getNativesForFlavors(DataFlavor[] dfs)
        {
            java.awt.datatransfer.FlavorMap m = java.awt.datatransfer.SystemFlavorMap.getDefaultFlavorMap();
            return m.getNativesForFlavors(dfs);
        }

        @Override
        public java.util.Map<String, DataFlavor> getFlavorsForNatives(String[] natives)
        {
            java.awt.datatransfer.FlavorMap m = java.awt.datatransfer.SystemFlavorMap.getDefaultFlavorMap();
            return m.getFlavorsForNatives(natives);
        }
    }


/*	private void dumpBytesOfGif(String imageFileName) {
        byte[] data = new byte[10000];
		try {
			BufferedInputStream in=new BufferedInputStream(getClass().getResourceAsStream(imageFileName));
			int count = in.read(data);
			Image image = Toolkit.getDefaultToolkit().createImage(data);
			PixelGrabber pg = new PixelGrabber(image, 0, 0, 16, 16, false);
			try {
				pg.grabPixels();
				byte[] pixels = (byte[])pg.getPixels();
				int pixelRow = 0;
				for (int i=0; i<pixels.length; i++) {
					if ((i%16) == 0)
						pixelRow = 0;
					pixelRow = (pixelRow<<2) + pixels[i];
					if ((i%16) == 15)
						System.out.print("0x"+Integer.toHexString(pixelRow)+", ");
					if ((i%80) == 79)
						System.out.println();
					}
				System.out.println("},{");
				}
			catch (InterruptedException e) {
				System.out.println("Pixel Grabbing interrupted: "+e);
				}
			}
		catch (Exception e) {
			System.out.println("Error reading data: "+e);
			}
		}*/
}

